diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..227819e --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet --version:*)" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md index 3c34100..ad8bb5f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 --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 diff --git a/KoogleApp.sln b/KoogleApp.sln index 1715d62..910bb83 100644 --- a/KoogleApp.sln +++ b/KoogleApp.sln @@ -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 diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..004de3d --- /dev/null +++ b/docs/IMPLEMENTATION_PLAN.md @@ -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, 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() 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 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 diff --git a/docs/build.md b/docs/build.md index 70a0fda..6befa89 100644 --- a/docs/build.md +++ b/docs/build.md @@ -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 ausfhren +## Tests ausf�hren ``` dotnet test tests/Koogle.Application.Tests -``` \ No newline at end of file +``` + + diff --git a/src/Koogle.Domain/Enums/UserRole.cs b/src/Koogle.Domain/Enums/UserRole.cs index 1d355f0..e4e2e94 100644 --- a/src/Koogle.Domain/Enums/UserRole.cs +++ b/src/Koogle.Domain/Enums/UserRole.cs @@ -9,6 +9,10 @@ namespace Koogle.Domain.Enums /// /// different user roles within the application. /// + /// + /// 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. + /// public static class UserRole { /// @@ -27,7 +31,7 @@ namespace Koogle.Domain.Enums public const string Editor = "Editor"; /// - /// with this role, the user can only view content within a club. + /// With this role, the user can only view content within a club. /// public const string Viewer = "Viewer"; diff --git a/src/Koogle.Domain/Interfaces/IClubRepository.cs b/src/Koogle.Domain/Interfaces/IClubRepository.cs new file mode 100644 index 0000000..b15d8da --- /dev/null +++ b/src/Koogle.Domain/Interfaces/IClubRepository.cs @@ -0,0 +1,12 @@ +using Koogle.Domain.Entities; + +namespace Koogle.Domain.Interfaces; + +public interface IClubRepository +{ + Task> GetAllAsync(CancellationToken ct = default); + Task GetByIdAsync(Guid id, CancellationToken ct = default); + Task AddAsync(Club entity, CancellationToken ct = default); + Task UpdateAsync(Club entity, CancellationToken ct = default); + Task DeleteAsync(Guid id, CancellationToken ct = default); +} diff --git a/src/Koogle.Domain/Interfaces/IExpenseRepository.cs b/src/Koogle.Domain/Interfaces/IExpenseRepository.cs new file mode 100644 index 0000000..41a7af2 --- /dev/null +++ b/src/Koogle.Domain/Interfaces/IExpenseRepository.cs @@ -0,0 +1,12 @@ +using Koogle.Domain.Entities; + +namespace Koogle.Domain.Interfaces; + +public interface IExpenseRepository +{ + Task> GetByClubIdAsync(Guid clubId, CancellationToken ct = default); + Task GetByIdAsync(Guid id, CancellationToken ct = default); + Task AddAsync(Expense entity, CancellationToken ct = default); + Task UpdateAsync(Expense entity, CancellationToken ct = default); + Task DeleteAsync(Guid id, CancellationToken ct = default); +} diff --git a/src/Koogle.Domain/Interfaces/IPersonExpenseRepository.cs b/src/Koogle.Domain/Interfaces/IPersonExpenseRepository.cs new file mode 100644 index 0000000..0854578 --- /dev/null +++ b/src/Koogle.Domain/Interfaces/IPersonExpenseRepository.cs @@ -0,0 +1,12 @@ +using Koogle.Domain.Entities; + +namespace Koogle.Domain.Interfaces; + +public interface IPersonExpenseRepository +{ + Task> GetByDayIdAsync(Guid dayId, CancellationToken ct = default); + Task> GetByPersonIdAsync(Guid personId, CancellationToken ct = default); + Task GetByIdAsync(Guid id, CancellationToken ct = default); + Task AddAsync(PersonExpense entity, CancellationToken ct = default); + Task DeleteAsync(Guid id, CancellationToken ct = default); +} diff --git a/src/Koogle.Domain/Interfaces/IPersonRepository.cs b/src/Koogle.Domain/Interfaces/IPersonRepository.cs new file mode 100644 index 0000000..5baf661 --- /dev/null +++ b/src/Koogle.Domain/Interfaces/IPersonRepository.cs @@ -0,0 +1,12 @@ +using Koogle.Domain.Entities; + +namespace Koogle.Domain.Interfaces; + +public interface IPersonRepository +{ + Task> GetByClubIdAsync(Guid clubId, CancellationToken ct = default); + Task GetByIdAsync(Guid id, CancellationToken ct = default); + Task AddAsync(Person entity, CancellationToken ct = default); + Task UpdateAsync(Person entity, CancellationToken ct = default); + Task DeleteAsync(Guid id, CancellationToken ct = default); +}