diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md
index 6b945e7..63acc71 100644
--- a/docs/IMPLEMENTATION_PLAN.md
+++ b/docs/IMPLEMENTATION_PLAN.md
@@ -1703,7 +1703,7 @@ public enum CashBookEntryType { Income = 0, Expense = 1 }
| ✓ | K2 | Infrastructure | EF Configurations | 4 |
| ✓ | K3 | Infrastructure | Migration | - |
| ✓ | K4 | Security | Kassenwart Role + Policy | 4 |
-| ☐ | K5 | Infrastructure | Repositories | 5 |
+| ✓ | K5 | Infrastructure | Repositories | 5 |
| ☐ | K6 | Application | DTOs | 3 |
| ☐ | K7 | Application | Service Interfaces | 2 |
| ☐ | K8 | Application | Service Implementations | 4 |
diff --git a/src/Koogle.Domain/Interfaces/IBookingCategoryRepository.cs b/src/Koogle.Domain/Interfaces/IBookingCategoryRepository.cs
new file mode 100644
index 0000000..9fe9de5
--- /dev/null
+++ b/src/Koogle.Domain/Interfaces/IBookingCategoryRepository.cs
@@ -0,0 +1,34 @@
+using Koogle.Domain.Entities;
+
+namespace Koogle.Domain.Interfaces;
+
+///
+/// Repository interface for booking category operations.
+///
+public interface IBookingCategoryRepository
+{
+ ///
+ /// Gets all booking categories for a club.
+ ///
+ Task> GetByClubIdAsync(Guid clubId, bool includeInactive = false, CancellationToken ct = default);
+
+ ///
+ /// Gets a booking category by its identifier.
+ ///
+ Task GetByIdAsync(Guid id, CancellationToken ct = default);
+
+ ///
+ /// Gets a system category by name for a club.
+ ///
+ Task GetSystemCategoryAsync(Guid clubId, string name, CancellationToken ct = default);
+
+ ///
+ /// Adds a new booking category.
+ ///
+ Task AddAsync(BookingCategory entity, CancellationToken ct = default);
+
+ ///
+ /// Updates an existing booking category.
+ ///
+ Task UpdateAsync(BookingCategory entity, CancellationToken ct = default);
+}
diff --git a/src/Koogle.Domain/Interfaces/ICashBookEntryRepository.cs b/src/Koogle.Domain/Interfaces/ICashBookEntryRepository.cs
new file mode 100644
index 0000000..db05f31
--- /dev/null
+++ b/src/Koogle.Domain/Interfaces/ICashBookEntryRepository.cs
@@ -0,0 +1,39 @@
+using Koogle.Domain.Entities;
+
+namespace Koogle.Domain.Interfaces;
+
+///
+/// Repository interface for cash book entry operations.
+///
+public interface ICashBookEntryRepository
+{
+ ///
+ /// Gets cash book entries for a club within optional date range.
+ ///
+ Task> GetByClubIdAsync(Guid clubId, DateTime? from, DateTime? to, CancellationToken ct = default);
+
+ ///
+ /// Gets a cash book entry by its identifier.
+ ///
+ Task GetByIdAsync(Guid id, CancellationToken ct = default);
+
+ ///
+ /// Calculates the balance for a club as of a specific date.
+ ///
+ Task GetBalanceAsync(Guid clubId, DateTime? asOfDate = null, CancellationToken ct = default);
+
+ ///
+ /// Adds a new cash book entry.
+ ///
+ Task AddAsync(CashBookEntry entity, CancellationToken ct = default);
+
+ ///
+ /// Gets cash book entries linked to a specific day.
+ ///
+ Task> GetByDayIdAsync(Guid dayId, CancellationToken ct = default);
+
+ ///
+ /// Checks if a membership fee entry exists for a specific month.
+ ///
+ Task ExistsMembershipFeeForMonthAsync(Guid clubId, int year, int month, CancellationToken ct = default);
+}
diff --git a/src/Koogle.Infrastructure/DependencyInjection.cs b/src/Koogle.Infrastructure/DependencyInjection.cs
index 32f6ecd..a347b12 100644
--- a/src/Koogle.Infrastructure/DependencyInjection.cs
+++ b/src/Koogle.Infrastructure/DependencyInjection.cs
@@ -92,6 +92,8 @@ public static class DependencyInjection
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
// Services
services.AddScoped();
diff --git a/src/Koogle.Infrastructure/Repositories/BookingCategoryRepository.cs b/src/Koogle.Infrastructure/Repositories/BookingCategoryRepository.cs
new file mode 100644
index 0000000..8f0c614
--- /dev/null
+++ b/src/Koogle.Infrastructure/Repositories/BookingCategoryRepository.cs
@@ -0,0 +1,67 @@
+using Koogle.Domain.Entities;
+using Koogle.Domain.Interfaces;
+using KoogleApp.Data;
+using Microsoft.EntityFrameworkCore;
+
+namespace Koogle.Infrastructure.Repositories;
+
+///
+/// Repository implementation for managing booking category entities.
+///
+/// The database context factory for creating scoped contexts.
+public class BookingCategoryRepository(IDbContextFactory contextFactory) : IBookingCategoryRepository
+{
+ ///
+ public async Task> GetByClubIdAsync(Guid clubId, bool includeInactive = false, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ var query = context.BookingCategories
+ .Where(c => c.ClubId == clubId && !c.IsDeleted);
+
+ if (!includeInactive)
+ {
+ query = query.Where(c => c.IsActive);
+ }
+
+ return await query
+ .OrderBy(c => c.Name)
+ .ToListAsync(ct);
+ }
+
+ ///
+ public async Task GetByIdAsync(Guid id, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ return await context.BookingCategories
+ .FirstOrDefaultAsync(c => c.Id == id && !c.IsDeleted, ct);
+ }
+
+ ///
+ public async Task GetSystemCategoryAsync(Guid clubId, string name, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ return await context.BookingCategories
+ .FirstOrDefaultAsync(c => c.ClubId == clubId && c.IsSystemCategory && c.Name == name && !c.IsDeleted, ct);
+ }
+
+ ///
+ public async Task AddAsync(BookingCategory entity, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ entity.Id = Guid.NewGuid();
+ entity.CreatedAt = DateTime.UtcNow;
+ await context.BookingCategories.AddAsync(entity, ct);
+ await context.SaveChangesAsync(ct);
+ return entity;
+ }
+
+ ///
+ public async Task UpdateAsync(BookingCategory entity, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ entity.ModifiedAt = DateTime.UtcNow;
+ context.BookingCategories.Update(entity);
+ await context.SaveChangesAsync(ct);
+ return entity;
+ }
+}
diff --git a/src/Koogle.Infrastructure/Repositories/CashBookEntryRepository.cs b/src/Koogle.Infrastructure/Repositories/CashBookEntryRepository.cs
new file mode 100644
index 0000000..04cc603
--- /dev/null
+++ b/src/Koogle.Infrastructure/Repositories/CashBookEntryRepository.cs
@@ -0,0 +1,129 @@
+using Koogle.Domain.Entities;
+using Koogle.Domain.Enums;
+using Koogle.Domain.Interfaces;
+using KoogleApp.Data;
+using Microsoft.EntityFrameworkCore;
+
+namespace Koogle.Infrastructure.Repositories;
+
+///
+/// Repository implementation for managing cash book entry entities.
+///
+/// The database context factory for creating scoped contexts.
+public class CashBookEntryRepository(IDbContextFactory contextFactory) : ICashBookEntryRepository
+{
+ ///
+ public async Task> GetByClubIdAsync(Guid clubId, DateTime? from, DateTime? to, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ IQueryable query = context.CashBookEntries
+ .Where(e => e.ClubId == clubId && !e.IsDeleted);
+
+ if (from.HasValue)
+ {
+ query = query.Where(e => e.BookingDate >= from.Value);
+ }
+
+ if (to.HasValue)
+ {
+ query = query.Where(e => e.BookingDate <= to.Value);
+ }
+
+ return await query
+ .Include(e => e.Category)
+ .Include(e => e.Person)
+ .Include(e => e.Day)
+ .OrderByDescending(e => e.BookingDate)
+ .ThenByDescending(e => e.CreatedAt)
+ .ToListAsync(ct);
+ }
+
+ ///
+ public async Task GetByIdAsync(Guid id, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ return await context.CashBookEntries
+ .Include(e => e.Category)
+ .Include(e => e.Person)
+ .Include(e => e.Day)
+ .FirstOrDefaultAsync(e => e.Id == id && !e.IsDeleted, ct);
+ }
+
+ ///
+ public async Task GetBalanceAsync(Guid clubId, DateTime? asOfDate = null, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+
+ // Get initial balance from club
+ var club = await context.Clubs.FirstOrDefaultAsync(c => c.Id == clubId, ct);
+ var initialBalance = club?.InitialBalance ?? 0m;
+
+ var query = context.CashBookEntries
+ .Where(e => e.ClubId == clubId && !e.IsDeleted);
+
+ if (asOfDate.HasValue)
+ {
+ query = query.Where(e => e.BookingDate <= asOfDate.Value);
+ }
+
+ var entries = await query.ToListAsync(ct);
+
+ var income = entries
+ .Where(e => e.EntryType == CashBookEntryType.Income)
+ .Sum(e => e.Amount);
+
+ var expense = entries
+ .Where(e => e.EntryType == CashBookEntryType.Expense)
+ .Sum(e => e.Amount);
+
+ return initialBalance + income - expense;
+ }
+
+ ///
+ public async Task AddAsync(CashBookEntry entity, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ entity.Id = Guid.NewGuid();
+ entity.CreatedAt = DateTime.UtcNow;
+ await context.CashBookEntries.AddAsync(entity, ct);
+ await context.SaveChangesAsync(ct);
+ return entity;
+ }
+
+ ///
+ public async Task> GetByDayIdAsync(Guid dayId, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ return await context.CashBookEntries
+ .Where(e => e.DayId == dayId && !e.IsDeleted)
+ .Include(e => e.Category)
+ .Include(e => e.Person)
+ .OrderByDescending(e => e.CreatedAt)
+ .ToListAsync(ct);
+ }
+
+ ///
+ public async Task ExistsMembershipFeeForMonthAsync(Guid clubId, int year, int month, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+
+ // Get the system category for membership fees
+ var membershipCategory = await context.BookingCategories
+ .FirstOrDefaultAsync(c => c.ClubId == clubId && c.IsSystemCategory && c.Name == "Mitgliedsbeitrag" && !c.IsDeleted, ct);
+
+ if (membershipCategory == null)
+ {
+ return false;
+ }
+
+ var startOfMonth = new DateTime(year, month, 1);
+ var endOfMonth = startOfMonth.AddMonths(1).AddDays(-1);
+
+ return await context.CashBookEntries
+ .AnyAsync(e => e.ClubId == clubId
+ && e.CategoryId == membershipCategory.Id
+ && e.BookingDate >= startOfMonth
+ && e.BookingDate <= endOfMonth
+ && !e.IsDeleted, ct);
+ }
+}