added claude planning

This commit is contained in:
beo3000 2025-12-22 21:14:54 +01:00
parent 9491ff26d8
commit b9dd2b2af0
10 changed files with 878 additions and 68 deletions

View File

@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(dotnet --version:*)"
]
}
}

162
CLAUDE.md
View File

@ -2,87 +2,119 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Test Commands
## Project Overview
Blazor Server app (.NET 9.0) with Clean Architecture: Domain, Application, Infrastructure, Web layers. Uses ASP.NET Core Identity with dual DbContext pattern (AppDbContext for domain, AppIdentityDbContext for auth), MudBlazor UI, and Fluxor state management.
The name for the App is KOOGLE. Koogle is an app for club management.
### Functional
- Club management
- Management of match days and games
- Registration of participants (members and guests)
- Automatic or rule-based cost allocation
- Evaluation per person/day/game
- User-related actions (CurrentUserService)
### Technical
- ASP.NET Core backend
- EF Core
- Multi-client capable (club as scope)
- Future web or mobile app realistic
### Target group
- primarily Bowling clubs (Kegelclub / Kegelvereine) - In German "Kegeln" is a bit different to bowling
- maybe in future also for darts, soccer betting pools, Leisure groups, Sport clubs, etc.
## Implementation Plan
**Phase 1 MVP:** See [docs/IMPLEMENTATION_PLAN.md](./docs/IMPLEMENTATION_PLAN.md)
- 23 granular phases (A1-F3)
- Foundation → Clubs → Users → Persons → Days → Dashboard
- Track progress via checkboxes in plan
- ~75 files total
## Architecture
**Clean Architecture Layers:**
- `Koogle.Domain`: Entities, Enums, Interfaces (no dependencies)
- `Koogle.Application`: DTOs, Services, Mapping (depends on Domain, Infrastructure)
- `Koogle.Infrastructure`: DbContexts, Repositories, Identity, Security (depends on Domain)
- `Koogle.Web`: Blazor components, Controllers, Fluxor Store (depends on Application)
- `KoogleApp`: Is a former approach, and not part of the project. We just use it to copy stuff into the actual project.
**Dual DbContext Pattern:**
- `AppDbContext`: Domain entities → `app` schema → `__EFMigrationsHistory_App`
- `AppIdentityDbContext`: ASP.NET Identity → `auth` schema → `__EFMigrationsHistory_Auth`
**Key Technologies:**
- Fluxor (Redux pattern): Store in `src/Koogle.Web/Store/`, Actions/Reducers/Effects pattern
- ASP.NET Identity: Custom `ApplicationUser`, `ApplicationRole`, `CustomClaimsPrincipalFactory`
- Authorization: Club-based roles (Viewer/Editor/Admin) via `ClubRoleRequirement`/`ClubRoleHandler`
- AutoMapper (Application layer), MudBlazor UI components
## Common Commands
### Build & Run
```bash
# Build solution
dotnet build KoogleApp.sln
# Run the application
dotnet run --project KoogleApp/KoogleApp.csproj
# Run all tests
dotnet test
# Run specific test class
dotnet test --filter "FullyQualifiedName~Koogle.Tests.ReducerTests.ThrowPanelStateTests"
# Run specific test method
dotnet test --filter "FullyQualifiedName~Koogle.Tests.GameTraining.GameTrainingServiceTest.ServiceWorksCorrectly"
dotnet build
dotnet run --project src/Koogle.Web
```
## Entity Framework Migrations
### Database Migrations
**AppDbContext (domain data):**
```bash
# Create migration
dotnet ef migrations add <MigrationName> --project ./KoogleApp/KoogleApp.csproj
dotnet ef migrations add [Name] --project src/Koogle.Infrastructure --startup-project src/Koogle.Web --context AppDbContext --output-dir Data/Migrations
# Update database (Development)
dotnet ef --startup-project ./KoogleApp/KoogleApp.csproj --project ./KoogleApp/KoogleApp.csproj database update -- Development
# Apply migration
dotnet ef database update -p src/Koogle.Infrastructure -s src/Koogle.Web --context AppDbContext
```
## Architecture Overview
**AppIdentityDbContext (auth data):**
```bash
# Create migration
dotnet ef migrations add [Name] --project src/Koogle.Infrastructure --startup-project src/Koogle.Web --context AppIdentityDbContext --output-dir Identity/Migrations
This is a **Blazor Server** application (.NET 9) for tracking a 9-pin bowling (Kegeln) game. It uses:
- **Fluxor** for state management (Redux pattern)
- **MudBlazor** for UI components
- **SignalR** for real-time synchronization between clients
- **Entity Framework Core** with SQL Server
# Apply migration
dotnet ef database update -p src/Koogle.Infrastructure -s src/Koogle.Web --context AppIdentityDbContext
```
### Fluxor Store Structure (`Store/`)
**Update EF Tools:**
```bash
dotnet tool update --global dotnet-ef --version 9.0.11
```
The state is organized into feature slices following the Redux pattern:
- **`Store/Game/`** - Core game state management
- `ThrowPanel/` - Pin states (9 pins), throw counter, throw mode (Reposition/Decrease)
- `Participants/` - Active players and turn order
- `Setup/` - Game configuration state
- `UndoRedo/` - History management for game actions
- `Menus/` - Game-specific menu actions
- `ThrowTimer/` - Timer state for throws
- **`Store/Player/`** - Player management (CRUD)
- **`Store/DayFeature/`** - Day/session management
- **`Store/ModelFeature/`** - Shared model state
### Testing
```bash
dotnet test test/Koogle.Tests
```
Test framework: xUnit with FluentAssertions, Moq
Each feature contains: `State.cs`, `Actions.cs`, `Reducers.cs`, `Effects.cs`, `Selectors.cs`
## Development Notes
### Game Plugin System (`Games/`)
**DependencyInjection:**
- Each layer has `DependencyInjection.cs` registering services via extension methods
- Call order in `Program.cs`: AddInfrastructure → AddApplication → AddFluxor
Games are implemented as plugins via `IKnownGame` interface:
- `IKnownGame` - Defines game metadata and component types
- `IGameService` - Handles game logic (throws, player progression)
- `IGameSetupModel` / `IGameModel` / `IGameState` - Data contracts with JSON serialization support
**Authentication Flow:**
- Cookie-based auth (`/login` path)
- `CurrentUserService` (ICurrentUserService) provides user context
- `BootstrapSeeder` seeds super-admin, `IdentityRoleSeeder` seeds roles
- Blazor: `AuthStateInitializer.razor` syncs auth state, Fluxor `AuthState` for client state
Current implementations:
- `Games/Training/` - Training mode
- `Games/Shit/` - "Shit" game variant
**Authorization:**
- Custom club-based policies: `ClubViewer`, `ClubEditor`, `ClubAdmin`
- `ICurrentClubContext` provides club context for authorization decisions
- `ClubRoleHandler` evaluates requirements based on user's club membership
To add a new game:
1. Create folder in `Games/` with `IKnownGame` implementation
2. Create setup component (Razor), board component, and `IGameService` implementation
3. Add JSON type discriminator to `IGameState` and `IGameSetupModel` interfaces
**Blazor Components:**
- Interactive server mode
- Layout: `MainLayout.razor`, `NavMenu.razor`
- Auth components: `AuthTest.razor`, `LogoutButton.razor`, `Account/Login.razor`
### Key Models
- `ThrowPanelState` - Contains 9 pin states (`PinStatus`: Standing/Fallen/Disabled), throw mode, throw counter
- `GameProgress` - Tracks state transitions during a throw (before/after states)
- `ParticipantsState` - Player order and turn tracking
### Real-time Sync
`SharedModelHub` (SignalR) broadcasts state changes between connected clients. `HubConnectionService` manages client-side connections.
### Services Registration
`Services/ServiceExtension.cs` contains DI registration:
- `AddDbServices()` - Database and Identity
- `AddAppServices()` - Fluxor, repositories, game services (auto-discovered via reflection)
**Connection String:**
- Key: `AppDb` in `appsettings.json`
- Both contexts share same connection, different schemas/migration tables

View File

@ -27,6 +27,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{63E5B7FE-38C2-47E8-9CF8-AB846B5B4281}"
ProjectSection(SolutionItems) = preProject
docs\build.md = docs\build.md
CLAUDE.md = CLAUDE.md
todos.md = todos.md
EndProjectSection
EndProject

710
docs/IMPLEMENTATION_PLAN.md Normal file
View File

@ -0,0 +1,710 @@
# Koogle App - Analyse & Vorschlag für Bereiche und Pages
## Zweck der Anwendung (abgeleitet aus Datenmodell)
**Koogle** ist eine Vereinsverwaltung für Kegelvereine mit Schwerpunkt auf:
### Kernfunktionen
1. **Vereinsverwaltung**: Multi-Mandanten-System, jeder Verein = eigener Scope
2. **Mitglieder & Gäste**: Verwaltung von Personen mit Status (Member/Guest)
3. **Spieltagsorganisation**: Planung und Durchführung von Kegeltagen (Days)
- Status-Workflow: New → Started → Closed (oder Postponed)
- Zuordnung von Teilnehmern pro Spieltag
4. **Spielverwaltung**: Mehrere Games pro Day möglich
- JSON-basierte Spielstände (GameData)
- Teilnehmerzuordnung pro Game
5. **Kostenmanagement**:
- Vordefinierte Kosten/Strafen (Expenses) pro Verein
- Automatische Trigger (z.B. Pudel, Pumpe, Aus, Kranz, etc.)
- Variable/Fixe Preise, Inverse Kosten (alle außer einem zahlen)
- One-Click Kosten für schnelle Erfassung
6. **Abrechnung**:
- Pro Person, pro Tag, pro Spiel
- Berechnungsmethoden: None, Average, Maximum (für fehlende Personen)
- Status: Open/Done für PersonExpenses
7. **Benutzer & Berechtigungen**:
- ASP.NET Identity mit Custom UserProfile
- Rollen pro Verein: Viewer, Editor, Admin
- SuperAdmin für vereinsübergreifende Verwaltung
- Multi-Club Zugehörigkeit möglich
---
## Bestehende Implementierung
### Vollständig implementiert
- ✅ Authentication/Authorization Framework
- ✅ Login mit Vereinsauswahl
- ✅ Fluxor State Management (AuthState)
- ✅ Rollenbasierte Berechtigungen
- ✅ MudBlazor UI Framework
- ✅ Dual DbContext (Domain + Identity)
- ✅ Clean Architecture Struktur
- ✅ Seeders (SuperAdmin, Roles)
### Teilweise implementiert
- ⚠️ DayService (vorhanden, aber auskommentiert)
- ⚠️ Navigation (Skelett vorhanden, kaum Menüpunkte)
### Nicht implementiert
- ❌ UI für Clubs, Days, Games, Persons, Expenses
- ❌ Services für Person, Game, Expense
- ❌ DTOs für die meisten Domain Entities
- ❌ Fluxor States für Domain-Daten
- ❌ Reporting/Export-Funktionalität
---
## Vorgeschlagene Bereiche & Pages
### 1. DASHBOARD-BEREICH (Startseite)
**Route:** `/` oder `/dashboard`
**Zweck:** Übersicht über aktuelle Aktivitäten im ausgewählten Verein
**Pages:**
- **Dashboard.razor**:
- Nächste geplante Spieltage
- Aktuelle offene Kosten
- Quick-Actions (neuer Spieltag, neue Person)
- Statistiken (Anzahl Mitglieder, Gäste, offene Abrechnungen)
---
### 2. SPIELTAG-BEREICH (Day Management)
**Route:** `/days`
**Pages:**
- **DayList.razor** (`/days`):
- Tabelle: PostDate, Status, Teilnehmeranzahl
- Filter: Jahr, Monat, Status
- Actions: Neu, Bearbeiten, Löschen, Schließen
- **DayDetail.razor** (`/days/{id}`):
- Spieltag-Info (Datum, Status)
- Teilnehmerliste mit Anwesenheit
- Games des Tages
- PersonExpenses des Tages (Übersicht)
- Actions: Teilnehmer hinzufügen, Spiel hinzufügen, Status ändern
- **DayCreate.razor** (`/days/new`):
- Formular: Datum, Vorauswahl Teilnehmer aus Mitgliedern
- **DayEdit.razor** (`/days/{id}/edit`):
- Datum ändern, Teilnehmer hinzufügen/entfernen
---
### 3. SPIEL-BEREICH (Game Management)
**Route:** `/days/{dayId}/games`
**Pages:**
- **GameList.razor** (`/days/{dayId}/games`):
- Liste der Spiele eines Spieltags
- Actions: Neues Spiel, Bearbeiten, Löschen
- **GameDetail.razor** (`/days/{dayId}/games/{gameId}`):
- GameData anzeigen/bearbeiten (JSON-Editor oder strukturierte Eingabe)
- Teilnehmer des Spiels
- Kosten des Spiels (PersonExpenses mit GameId)
- Trigger-Events erfassen (z.B. "Pudel" → Expense zuweisen)
- **GameCreate.razor** (`/days/{dayId}/games/new`):
- Teilnehmer auswählen (aus DayPersons)
- Optionale GameData-Initialisierung
---
### 4. KOSTEN-BEREICH (Expense Management)
**Route:** `/expenses`
**Pages:**
- **ExpenseList.razor** (`/expenses`):
- Vordefinierte Expenses des Vereins
- Spalten: Name, Preis, Typ, IsOneClick, IsInverse, IsVariable
- Actions: Neu, Bearbeiten, Löschen
- **ExpenseCreate.razor** (`/expenses/new`):
- Formular für neue Expense-Vorlage
- Trigger-Zuordnung optional
- **PersonExpenseList.razor** (`/expenses/assigned`):
- Alle zugewiesenen Kosten (PersonExpenses)
- Filter: Person, Tag, Status (Open/Done), Datum
- Bulk-Actions: Als bezahlt markieren, Löschen
- **ExpenseTriggerConfig.razor** (`/expenses/triggers`):
- Trigger-Typen anzeigen
- Zuordnung Trigger → Expense
---
### 5. PERSONEN-BEREICH (Person Management)
**Route:** `/people`
**Pages:**
- **PersonList.razor** (`/people`):
- Tabelle: Name, Status (Member/Guest), Actions
- Filter: Status
- Actions: Neu, Bearbeiten, Löschen
- **PersonCreate.razor** (`/people/new`):
- Name, Status
- **PersonDetail.razor** (`/people/{id}`):
- Personen-Info
- Teilnahme-Historie (DayPersons)
- Kosten-Historie (PersonExpenses)
- Statistiken (Gesamtkosten, Anzahl Teilnahmen)
---
### 6. AUSWERTUNGEN-BEREICH (Reports/Evaluations)
**Route:** `/reports`
**Pages:**
- **ReportOverview.razor** (`/reports`):
- Auswahl: Pro Person, Pro Tag, Pro Spiel
- **PersonReport.razor** (`/reports/person/{id}`):
- Alle Kosten einer Person
- Summen pro Tag, pro Spiel
- Zeitraum-Filter
- **DayReport.razor** (`/reports/day/{id}`):
- Alle Kosten eines Spieltags
- Aufschlüsselung pro Person
- Export-Option (PDF, CSV)
- **PeriodReport.razor** (`/reports/period`):
- Zeitraum wählen (von-bis)
- Aggregierte Statistiken
- Top-Spieler, teuerste Tage, etc.
---
### 7. STAMMDATEN-BEREICH (Master Data - Vereins-spezifisch)
**Route:** `/masterdata`
**Pages:**
- **ClubSettings.razor** (`/masterdata/club`):
- Vereins-Name
- ExpenseCalculation-Methode
- Weitere Einstellungen
- **ExpenseTemplates.razor** (`/masterdata/expenses`):
- Siehe Expense-Bereich (evtl. Duplikat)
- **TriggerConfig.razor** (`/masterdata/triggers`):
- Siehe ExpenseTriggerConfig
---
### 8. ADMIN-BEREICH (SuperAdmin - Vereinsübergreifend)
**Route:** `/admin`
**Pages:**
- **ClubList.razor** (`/admin/clubs`):
- Alle Vereine (SuperAdmin only)
- Actions: Neu, Bearbeiten, Löschen
- **ClubCreate.razor** (`/admin/clubs/new`):
- Name, ExpenseCalculation
- **UserManagement.razor** (`/admin/users`):
- Alle UserProfiles
- Vereinszuordnung (UserProfileClub)
- Rollen-Zuordnung pro Verein
- **SystemSettings.razor** (`/admin/system`):
- Globale Einstellungen
---
### 9. PROFIL-BEREICH (User Profile)
**Route:** `/profile`
**Pages:**
- **UserProfile.razor** (`/profile`):
- DisplayName, Locale, TimeZone ändern
- Passwort ändern
- Standard-Verein festlegen (UserProfileClub.IsDefault)
- Vereins-Mitgliedschaften anzeigen
---
## Vorgeschlagene Navigation-Struktur
```
NavMenu.razor:
├── 🏠 Dashboard (/)
├── 📅 Spieltage (/days)
├── 👥 Personen (/people)
├── 💰 Kosten
│ ├── Vorlagen (/expenses)
│ ├── Zugewiesen (/expenses/assigned)
│ └── Trigger (/expenses/triggers)
├── 📊 Auswertungen (/reports)
│ ├── Pro Person
│ ├── Pro Tag
│ └── Zeitraum
├── ⚙️ Stammdaten (IsClubEditor+)
│ ├── Verein (/masterdata/club)
│ └── Kosten-Vorlagen
├── 🔧 Admin (IsSuperAdmin)
│ ├── Vereine (/admin/clubs)
│ ├── Benutzer (/admin/users)
│ └── System (/admin/system)
└── 👤 Profil (/profile)
```
---
## Prioritäts-Vorschlag (Phasen) - basierend auf User-Feedback
### Phase 1: MVP - Basis-Verwaltung (DIESE PLANUNG)
**Scope:** User/Account-Mgmt, Club, Personen (Teilnehmer), Tage, Strafen
1. **User/Account-Verwaltung**
- User-Registrierung (self-service oder Admin)
- Passwort zurücksetzen
- User-zu-Club Zuordnung (UserProfileClub)
- Rollen pro Club zuweisen (UserProfileClubRoleAssignment)
- User-Profil (DisplayName, Locale, TimeZone)
2. **Club-Verwaltung** (SuperAdmin)
- Liste, Create, Edit, Delete
- ExpenseCalculation-Methode
3. **Person-Verwaltung** (Club-Teilnehmer: Members + Guests)
- Liste, Create, Edit, Delete
- Status (Member/Guest)
- **KEINE Login** - reine Stammdaten für Kegelclub
4. **Day-Verwaltung**
- Liste, Create, Edit, Delete
- Datum, Status (New, Started, Closed, Postponed)
- Teilnehmer zuordnen (DayPerson)
5. **PersonExpense - Strafen manuell erfassen**
- Pro Teilnehmer pro Tag Strafen hinzufügen
- Expense-Vorlagen (Name, Preis)
- Status: Open/Done
6. **Dashboard**
- Übersicht: Nächste Tage, offene Kosten
- Quick-Actions
7. **Einfache Auswertung**
- Pro Tag: Wer schuldet was
- Pro Person: Summe offener Kosten
### Phase 2: Detaillierte Spielverwaltung (SEPARATE PLANUNG SPÄTER)
**NICHT in dieser Planung:**
- Wurf-für-Wurf Eingabe
- Undo-Funktion
- Plugin-System für verschiedene Kegelspiele
- GameData strukturiert (JSON für jetzt)
- Automatische Trigger (Pudel, Pumpe, etc.)
- Expense-Trigger-Engine
### Phase 3: Advanced Features (SPÄTER)
- Export (PDF, CSV)
- Benutzer-Rollen verwalten
- Erweiterte Reports
---
---
# IMPLEMENTIERUNGSPLAN - Phase 1 MVP
## Umsetzungsreihenfolge - Übersicht
| ✓ | Phase | Bereich | Beschreibung | Dateien |
|---|-------|---------|--------------|---------|
| ☐ | A1 | Foundation | Repository Interfaces | 5 Interface-Dateien |
| ☐ | A2 | Foundation | Repository Implementations | 5 Repository-Dateien |
| ☐ | A3 | Foundation | DTOs | 5 DTO-Dateien |
| ☐ | A4 | Foundation | Service Interfaces | 5 Service-Interface-Dateien |
| ☐ | A5 | Foundation | Service Implementations | 5 Service-Dateien |
| ☐ | A6 | Foundation | AutoMapper Profiles | 5 Mapping-Dateien |
| ☐ | A7 | Foundation | DI Registration | 2 DI-Dateien ändern |
| ☐ | **B1** | **Clubs** | **ClubState Fluxor** | **4 State-Dateien** |
| ☐ | **B2** | **Clubs** | **Club Pages - ERSTES TESTBARES MVP** | **1 Razor** |
| ☐ | C1 | User/Account | UserService erweitern | 1 Service, 1 Interface, 1 DTO |
| ☐ | C2 | User/Account | Register Page | 1 Razor |
| ☐ | C3 | User/Account | Password Reset Pages | 2 Razor |
| ☐ | C4 | User/Account | Profile Page | 1 Razor |
| ☐ | C5 | User/Account | Admin Users Page | 1 Razor |
| ☐ | D1 | Personen | PersonState Fluxor | 4 State-Dateien |
| ☐ | D2 | Personen | Person Pages | 1 Razor |
| ☐ | D3 | Expenses | ExpenseState Fluxor | 4 State-Dateien |
| ☐ | D4 | Expenses | Expense Pages | 1 Razor |
| ☐ | E1 | Days | DayState Fluxor | 4 State-Dateien |
| ☐ | E2 | Days | Days List Page | 1 Razor |
| ☐ | E3 | Days | Day Details Page | 1 Razor |
| ☐ | E4 | Days | PersonExpense Management | Components in DayDetails |
| ☐ | F1 | Dashboard | Dashboard Page | 1 Razor |
| ☐ | F2 | Dashboard | Evaluation Components | 2 Shared Components |
| ☐ | F3 | Navigation | NavMenu finalisieren | 1 Razor ändern |
**Legende:** ☐ = Offen | ☑ = In Arbeit | ✓ = Fertig
**Geschätzte Dateien insgesamt: ~75 Dateien**
---
## Detaillierte Phasen
### **Phase A1: Repository Interfaces erstellen**
**Dateien:**
1. `src/Koogle.Domain/Interfaces/IClubRepository.cs`
2. `src/Koogle.Domain/Interfaces/IPersonRepository.cs`
3. `src/Koogle.Domain/Interfaces/IExpenseRepository.cs`
4. `src/Koogle.Domain/Interfaces/IDayRepository.cs` (erweitern)
5. `src/Koogle.Domain/Interfaces/IPersonExpenseRepository.cs`
**Methoden pro Interface:** GetAllAsync/GetByClubIdAsync, GetByIdAsync, AddAsync, UpdateAsync, DeleteAsync
---
### **Phase A2: Repository Implementations erstellen**
**Dateien:**
1. `src/Koogle.Infrastructure/Repositories/ClubRepository.cs`
2. `src/Koogle.Infrastructure/Repositories/PersonRepository.cs`
3. `src/Koogle.Infrastructure/Repositories/ExpenseRepository.cs`
4. `src/Koogle.Infrastructure/Repositories/DayRepository.cs`
5. `src/Koogle.Infrastructure/Repositories/PersonExpenseRepository.cs`
**Pattern:** IDbContextFactory<AppDbContext>, ClubId-Filter, Include Navigation Properties
---
### **Phase A3: DTOs erstellen**
**Dateien:**
1. `src/Koogle.Application/DTOs/ClubDto.cs` (ClubDto, CreateClubDto, UpdateClubDto)
2. `src/Koogle.Application/DTOs/PersonDto.cs` (PersonDto, CreatePersonDto, UpdatePersonDto)
3. `src/Koogle.Application/DTOs/ExpenseDto.cs` (ExpenseDto, CreateExpenseDto, UpdateExpenseDto)
4. `src/Koogle.Application/DTOs/DayDto.cs` erweitern (DayDto, CreateDayDto, UpdateDayDto, DayParticipantDto)
5. `src/Koogle.Application/DTOs/PersonExpenseDto.cs` (PersonExpenseDto, CreatePersonExpenseDto, DayEvaluationDto, PersonDayEvaluationDto)
---
### **Phase A4: Service Interfaces erstellen**
**Dateien:**
1. `src/Koogle.Application/Interfaces/IClubService.cs`
2. `src/Koogle.Application/Interfaces/IPersonService.cs`
3. `src/Koogle.Application/Interfaces/IExpenseService.cs`
4. `src/Koogle.Application/Interfaces/IDayService.cs` (erweitern)
5. `src/Koogle.Application/Interfaces/IPersonExpenseService.cs`
**Methoden:** GetAllAsync, GetByIdAsync, CreateAsync, UpdateAsync, DeleteAsync + spezifische Methoden
---
### **Phase A5: Service Implementations erstellen**
**Dateien:**
1. `src/Koogle.Application/Services/ClubService.cs`
2. `src/Koogle.Application/Services/PersonService.cs`
3. `src/Koogle.Application/Services/ExpenseService.cs`
4. `src/Koogle.Application/Services/DayService.cs` (erweitern)
5. `src/Koogle.Application/Services/PersonExpenseService.cs`
**Dependencies:** Repository, ICurrentClubContext, ICurrentUserService, IMapper
**Business Logic:** ClubId-Injection, Audit-Felder, Validierung
---
### **Phase A6: AutoMapper Profiles erstellen**
**Dateien:**
1. `src/Koogle.Application/Mapping/ClubMappingProfile.cs`
2. `src/Koogle.Application/Mapping/PersonMappingProfile.cs`
3. `src/Koogle.Application/Mapping/ExpenseMappingProfile.cs`
4. `src/Koogle.Application/Mapping/DayMappingProfile.cs`
5. `src/Koogle.Application/Mapping/PersonExpenseMappingProfile.cs`
**Pattern:** CreateMap<Entity, Dto>() bidirektional
---
### **Phase A7: DI Registration**
**Dateien ändern:**
1. `src/Koogle.Infrastructure/DependencyInjection.cs` (5 Repositories registrieren)
2. `src/Koogle.Application/DependencyInjection.cs` (5 Services registrieren)
---
### **Phase B1: UserService erweitern**
**Dateien:**
1. `src/Koogle.Application/Interfaces/IUserService.cs` erweitern
2. `src/Koogle.Application/Services/UserService.cs` erweitern
3. `src/Koogle.Application/DTOs/UserDto.cs` erweitern (RegisterUserDto, ResetPasswordDto, UpdateUserProfileDto)
**Neue Methoden:**
- RegisterUserAsync
- RequestPasswordResetAsync
- ResetPasswordAsync
- UpdateProfileAsync
- AssignUserToClubAsync, RemoveUserFromClubAsync
- AssignClubRoleAsync, RemoveClubRoleAsync
---
### **Phase B2: Register Page**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Account/Register.razor`
**Features:** Email, Password, DisplayName, Optional ClubName für initiale Zuordnung
---
### **Phase B3: Password Reset Pages**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Account/ForgotPassword.razor`
2. `src/Koogle.Web/Components/Pages/Account/ResetPassword.razor`
**Flow:** Email eingeben → Token per Email → Passwort zurücksetzen
---
### **Phase B4: Profile Page**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Account/Profile.razor`
**Features:** DisplayName, Locale, TimeZone, Club-Memberships anzeigen, Standard-Club setzen
---
### **Phase B5: Admin Users Page**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Admin/Users.razor`
**Features:** User-Liste, Club-Zuordnungen, Rollen pro Club
---
### **Phase C1: ClubState (Fluxor)**
**Dateien:**
1. `src/Koogle.Web/Store/ClubState/ClubState.cs`
2. `src/Koogle.Web/Store/ClubState/ClubActions.cs`
3. `src/Koogle.Web/Store/ClubState/ClubReducers.cs`
4. `src/Koogle.Web/Store/ClubState/ClubEffects.cs`
**Actions:** Load, Create, Update, Delete
---
### **Phase C2: Club Pages**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Admin/Clubs.razor`
**Features:** MudTable, CRUD-Dialogs, SuperAdmin only
---
### **Phase D1: PersonState (Fluxor)**
**Dateien:**
1. `src/Koogle.Web/Store/PersonState/PersonState.cs`
2. `src/Koogle.Web/Store/PersonState/PersonActions.cs`
3. `src/Koogle.Web/Store/PersonState/PersonReducers.cs`
4. `src/Koogle.Web/Store/PersonState/PersonEffects.cs`
---
### **Phase D2: Person Pages**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Persons/Persons.razor`
**Features:** MudTable mit Filter (Member/Guest), CRUD
---
### **Phase D3: ExpenseState (Fluxor)**
**Dateien:**
1. `src/Koogle.Web/Store/ExpenseState/ExpenseState.cs`
2. `src/Koogle.Web/Store/ExpenseState/ExpenseActions.cs`
3. `src/Koogle.Web/Store/ExpenseState/ExpenseReducers.cs`
4. `src/Koogle.Web/Store/ExpenseState/ExpenseEffects.cs`
---
### **Phase D4: Expense Pages**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Expenses/Expenses.razor`
**Features:** MudTable mit Expense-Vorlagen, CRUD
---
### **Phase E1: DayState (Fluxor)**
**Dateien:**
1. `src/Koogle.Web/Store/DayState/DayState.cs`
2. `src/Koogle.Web/Store/DayState/DayActions.cs`
3. `src/Koogle.Web/Store/DayState/DayReducers.cs`
4. `src/Koogle.Web/Store/DayState/DayEffects.cs`
**State:** Days, SelectedDay, SelectedDayExpenses, AvailablePersons
---
### **Phase E2: Days List Page**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Days/Days.razor`
**Features:** MudTable mit Jahr-Filter, Create Day
---
### **Phase E3: Day Details Page**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Days/DayDetails.razor`
**Features:**
- Day-Header (Datum, Status)
- Status-Workflow Buttons (New→Started→Closed)
- Teilnehmer-Sektion (Add/Remove)
- PersonExpense-Sektion
---
### **Phase E4: PersonExpense Management**
**Components in DayDetails:**
- PersonExpense-Tabelle
- Add Expense Dialog (Person auswählen, Expense auswählen, Preis editierbar wenn IsVariable)
- Delete PersonExpense (nur in New/Started)
- IsInverse Logic: 1 Person auswählen → alle anderen bekommen Expense
---
### **Phase F1: Dashboard Page**
**Dateien:**
1. `src/Koogle.Web/Components/Pages/Dashboard.razor`
**Features:** Summary Cards, Recent Days, Top Penalty Recipients
---
### **Phase F2: Evaluation Components**
**Dateien:**
1. `src/Koogle.Web/Components/Shared/DayEvaluationComponent.razor`
2. `src/Koogle.Web/Components/Shared/PersonEvaluationComponent.razor`
---
### **Phase F3: Navigation finalisieren**
**Dateien ändern:**
1. `src/Koogle.Web/Components/Layout/NavMenu.razor`
**Features:** Dashboard, Spieltage, Stammdaten, Admin, Profil
---
## Zentrale Anforderungen (User-Feedback)
### Business Rules
1. **PersonExpense.Price**: Bei IsVariable=true editierbar, aber vorbelegt aus Expense
2. **Day Status-Workflow**: Strikt New→Started→Closed, keine Sprünge
3. **User-Registrierung**: Self-Service (öffentlich)
4. **Passwort-Reset**: Email-basiert mit Token
5. **Day-Create**: Alle Members vorbelegt, Gäste manuell
6. **PersonExpense löschen**: Nur in Status New/Started
7. **Expense.IsInverse**: Automatisch - 1 Person auswählen, alle anderen bekommen Expense
8. **Dashboard Zeitraum**: Aktuelles Jahr
---
## Code-Patterns & Konventionen
### Repository Pattern
- Interface in Domain/Interfaces
- Implementation in Infrastructure/Repositories
- Standard-Methoden: GetAllAsync, GetByIdAsync, AddAsync, UpdateAsync, DeleteAsync
- ClubId-Filter via ICurrentClubContext
- IDbContextFactory<AppDbContext> für Scoping
### Service Pattern
- Interface in Application/Interfaces
- Implementation in Application/Services
- Dependencies: Repository, ICurrentClubContext, ICurrentUserService, IMapper
- ClubService: IsSuperAdmin Check
- Audit-Felder auto-setzen (CreatedById, CreatedAt, ModifiedById, ModifiedAt)
### Fluxor Pattern (Redux)
- State: Record mit Collections + IsLoading + Error
- Actions: Record per Operation (Load, LoadSuccess, LoadFailure, Create, Update, Delete)
- Reducers: Pure functions, State transformieren
- Effects: Async Operations, Service-Calls, Dispatcher
### UI Pattern (Blazor + MudBlazor)
- @inherits FluxorComponent
- @attribute [Authorize(Policy = "...")]
- MudTable für Listen
- MudDialog für Create/Edit
- MudForm mit Validation
---
## Referenzdateien für Patterns
**Service-Pattern:**
- `src/Koogle.Application/Services/UserService.cs`
**Fluxor-Pattern:**
- `src/Koogle.Web/Store/AuthState/AuthState.cs`
- `src/Koogle.Web/Store/AuthState/AuthActions.cs`
- `src/Koogle.Web/Store/AuthState/AuthReducers.cs`
- `src/Koogle.Web/Store/AuthState/AuthEffects.cs`
**AutoMapper:**
- `src/Koogle.Application/Mapping/UserProfile.cs`
**DI:**
- `src/Koogle.Infrastructure/DependencyInjection.cs`
- `src/Koogle.Application/DependencyInjection.cs`
**Domain:**
- `src/Koogle.Domain/Entities/BaseEntity.cs`
---
## Berechtigungen pro Feature
- **Club-Verwaltung**: SuperAdmin only
- **Person/Expense/Day CRUD**: ClubEditor+
- **Dashboard/Auswertungen**: ClubViewer+
Policy: `@attribute [Authorize(Policy = "ClubViewer|ClubEditor|ClubAdmin")]`
---
## Zusammenfassung
**23 feine Phasen** → **~75 Dateien** → **MVP Phase 1 komplett**
Bereit für Implementierung Phase A1

View File

@ -1,3 +1,9 @@
# Build & Development Documentation
## Implementation Plan
See [IMPLEMENTATION_PLAN.md](./IMPLEMENTATION_PLAN.md) for detailed Phase 1 MVP roadmap (23 phases, ~75 files).
---
# Entity Framework Core - Datenbank Migrationen und Updates
@ -32,7 +38,9 @@ dotnet ef database update -p src/Koogle.Infrastructure -s src/Koogle.Web --conte
# Tests
## Tests ausführen
## Tests ausf<EFBFBD>hren
```
dotnet test tests/Koogle.Application.Tests
```
```

View File

@ -9,6 +9,10 @@ namespace Koogle.Domain.Enums
/// <summary>
/// different user roles within the application.
/// </summary>
/// <remarks>
/// Even without these roles, users can just see content of days and games themselves have participated in.
/// Therefore, an optional connection between users and people within clubs is needed. [] TODO: Link users to a person in a club.
/// </remarks>
public static class UserRole
{
/// <summary>
@ -27,7 +31,7 @@ namespace Koogle.Domain.Enums
public const string Editor = "Editor";
/// <summary>
/// with this role, the user can only view content within a club.
/// With this role, the user can only view content within a club.
/// </summary>
public const string Viewer = "Viewer";

View File

@ -0,0 +1,12 @@
using Koogle.Domain.Entities;
namespace Koogle.Domain.Interfaces;
public interface IClubRepository
{
Task<List<Club>> GetAllAsync(CancellationToken ct = default);
Task<Club?> GetByIdAsync(Guid id, CancellationToken ct = default);
Task<Club> AddAsync(Club entity, CancellationToken ct = default);
Task<Club> UpdateAsync(Club entity, CancellationToken ct = default);
Task<bool> DeleteAsync(Guid id, CancellationToken ct = default);
}

View File

@ -0,0 +1,12 @@
using Koogle.Domain.Entities;
namespace Koogle.Domain.Interfaces;
public interface IExpenseRepository
{
Task<List<Expense>> GetByClubIdAsync(Guid clubId, CancellationToken ct = default);
Task<Expense?> GetByIdAsync(Guid id, CancellationToken ct = default);
Task<Expense> AddAsync(Expense entity, CancellationToken ct = default);
Task<Expense> UpdateAsync(Expense entity, CancellationToken ct = default);
Task<bool> DeleteAsync(Guid id, CancellationToken ct = default);
}

View File

@ -0,0 +1,12 @@
using Koogle.Domain.Entities;
namespace Koogle.Domain.Interfaces;
public interface IPersonExpenseRepository
{
Task<List<PersonExpense>> GetByDayIdAsync(Guid dayId, CancellationToken ct = default);
Task<List<PersonExpense>> GetByPersonIdAsync(Guid personId, CancellationToken ct = default);
Task<PersonExpense?> GetByIdAsync(Guid id, CancellationToken ct = default);
Task<PersonExpense> AddAsync(PersonExpense entity, CancellationToken ct = default);
Task<bool> DeleteAsync(Guid id, CancellationToken ct = default);
}

View File

@ -0,0 +1,12 @@
using Koogle.Domain.Entities;
namespace Koogle.Domain.Interfaces;
public interface IPersonRepository
{
Task<List<Person>> GetByClubIdAsync(Guid clubId, CancellationToken ct = default);
Task<Person?> GetByIdAsync(Guid id, CancellationToken ct = default);
Task<Person> AddAsync(Person entity, CancellationToken ct = default);
Task<Person> UpdateAsync(Person entity, CancellationToken ct = default);
Task<bool> DeleteAsync(Guid id, CancellationToken ct = default);
}