diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md
index fb82152..59f5e7b 100644
--- a/docs/IMPLEMENTATION_PLAN.md
+++ b/docs/IMPLEMENTATION_PLAN.md
@@ -1716,7 +1716,7 @@ public enum CashBookEntryType { Income = 0, Expense = 1 }
| ✓ | K15 | Web | Reports UI | 2 |
| ✓ | K16 | Application | Excel Export (ClosedXML) | 3 |
| ✓ | K17 | Application | PDF Export (QuestPDF) | 1 |
-| ☐ | K18 | Web | Export Controller | 1 |
+| ✓ | K18 | Web | Export Controller | 1 |
| ☐ | K19 | Web | Membership Fees Feature | 2 |
| ☐ | K20 | Web | Club Settings Extension | 3 |
| ☐ | K21 | Web | Navigation Integration | 1 |
diff --git a/src/Koogle.Web/Components/Pages/CashBook/Reports.razor.cs b/src/Koogle.Web/Components/Pages/CashBook/Reports.razor.cs
index 0eef36d..4809676 100644
--- a/src/Koogle.Web/Components/Pages/CashBook/Reports.razor.cs
+++ b/src/Koogle.Web/Components/Pages/CashBook/Reports.razor.cs
@@ -2,6 +2,7 @@ using Koogle.Application.DTOs;
using Koogle.Domain.Enums;
using Koogle.Web.Store.CashBookState;
using Microsoft.AspNetCore.Components;
+using Microsoft.JSInterop;
using MudBlazor;
namespace Koogle.Web.Components.Pages.CashBook;
@@ -11,6 +12,8 @@ namespace Koogle.Web.Components.Pages.CashBook;
///
public partial class Reports
{
+ [Inject] private IJSRuntime JS { get; set; } = default!;
+
private int _selectedMonth = DateTime.Today.Month;
private int _selectedYear = DateTime.Today.Year;
@@ -80,15 +83,15 @@ public partial class Reports
return type == CashBookEntryType.Income ? Color.Success : Color.Error;
}
- private void ExportExcel()
+ private async Task ExportExcel()
{
- // Will be implemented in K16
- Snackbar.Add("Excel-Export wird in einer späteren Version verfügbar sein", Severity.Info);
+ var url = $"/api/cashbook/export/excel?year={_selectedYear}&month={_selectedMonth}";
+ await JS.InvokeVoidAsync("open", url, "_blank");
}
- private void ExportPdf()
+ private async Task ExportPdf()
{
- // Will be implemented in K17
- Snackbar.Add("PDF-Export wird in einer späteren Version verfügbar sein", Severity.Info);
+ var url = $"/api/cashbook/export/pdf?year={_selectedYear}&month={_selectedMonth}";
+ await JS.InvokeVoidAsync("open", url, "_blank");
}
}
diff --git a/src/Koogle.Web/Controllers/CashBookController.cs b/src/Koogle.Web/Controllers/CashBookController.cs
new file mode 100644
index 0000000..be7738c
--- /dev/null
+++ b/src/Koogle.Web/Controllers/CashBookController.cs
@@ -0,0 +1,79 @@
+using Koogle.Application.Interfaces;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Koogle.Web.Controllers;
+
+///
+/// Controller for cash book export operations.
+///
+[Route("api/cashbook")]
+[Authorize(Policy = "ClubTreasurer")]
+public class CashBookController : Controller
+{
+ private readonly ICashBookService _cashBookService;
+ private readonly ICashBookExportService _exportService;
+ private readonly ICurrentClubContext _clubContext;
+
+ public CashBookController(
+ ICashBookService cashBookService,
+ ICashBookExportService exportService,
+ ICurrentClubContext clubContext)
+ {
+ _cashBookService = cashBookService;
+ _exportService = exportService;
+ _clubContext = clubContext;
+ }
+
+ ///
+ /// Exports cash book report to Excel format.
+ ///
+ [HttpGet("export/excel")]
+ public async Task ExportExcel([FromQuery] int year, [FromQuery] int? month)
+ {
+ var (from, to) = GetDateRange(year, month);
+ var report = await _cashBookService.GetReportAsync(from, to);
+
+ var bytes = await _exportService.ExportToExcelAsync(report, _clubContext.ClubName);
+ var fileName = GetFileName("xlsx", year, month);
+
+ return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
+ }
+
+ ///
+ /// Exports cash book report to PDF format.
+ ///
+ [HttpGet("export/pdf")]
+ public async Task ExportPdf([FromQuery] int year, [FromQuery] int? month)
+ {
+ var (from, to) = GetDateRange(year, month);
+ var report = await _cashBookService.GetReportAsync(from, to);
+
+ var bytes = await _exportService.ExportToPdfAsync(report, _clubContext.ClubName);
+ var fileName = GetFileName("pdf", year, month);
+
+ return File(bytes, "application/pdf", fileName);
+ }
+
+ private static (DateTime from, DateTime to) GetDateRange(int year, int? month)
+ {
+ if (month.HasValue)
+ {
+ var from = new DateTime(year, month.Value, 1);
+ var to = from.AddMonths(1).AddDays(-1);
+ return (from, to);
+ }
+ else
+ {
+ var from = new DateTime(year, 1, 1);
+ var to = new DateTime(year, 12, 31);
+ return (from, to);
+ }
+ }
+
+ private static string GetFileName(string extension, int year, int? month)
+ {
+ var period = month.HasValue ? $"{year}-{month:D2}" : $"{year}";
+ return $"Kassenbericht_{period}.{extension}";
+ }
+}