# Plan: Admin-Übersichten erweitern — Alle Mitgliedschaften & Alle Benutzer ## Task-Übersicht | ID | Beschreibung | Abhängigkeit | Typ | |----|-------------|--------------|-----| | T1 | User.java: createdAt-Feld hinzufügen | — | Backend | | T2 | AuthService.register(): createdAt setzen | T1 | Backend | | T3 | UserRepository: findAllByOrderByCreatedAtDesc() | T1 | Backend | | T4 | MembershipRepository: findAllWithDetails() | — | Backend | | T5 | MembershipService: getAllMemberships() | T4 | Backend | | T6 | MembershipController: GET /admin/memberships | T5 | Backend | | T7 | AdminIamService: getAllUsers() | T3 | Backend | | T8 | AdminIamController: GET /api/admin/users | T7 | Backend | | T9 | PendingMembershipResponse: validFrom/validTo erweitern | — | Backend | | T10 | Backend-Tests schreiben | T6, T8, T9 | Test | | T11 | OpenAPI-Spec generieren + Frontend-API-Client regenerieren | T6, T8 | Build | | T12 | Frontend MembershipService: getAllMemberships() | T11 | Frontend | | T13 | AdminMembershipsComponent: Tab-Navigation + Tabelle | T12, T9 | Frontend | | T14 | AdminUserApprovalComponent: Tab-Navigation + Tabelle | T11 | Frontend | | T15 | Integrations-Test: UI manuell prüfen | T13, T14 | Test | --- ## T1: User.java — createdAt-Feld hinzufügen **Beschreibung:** Füge der User-Entity ein `createdAt`-Feld vom Typ `LocalDateTime` hinzu. **Dateien:** - `eeg_backend/src/main/java/at/mueller/eeg/backend/iam/domain/User.java` **Akzeptanz:** - [ ] `createdAt` Feld mit `@Column(updatable = false)` vorhanden - [ ] Kein `@GeneratedValue` — wird manuell gesetzt - [ ] Kompiliert ohne Fehler - [ ] `ddl-auto: update` erstellt die Spalte automatisch **Details:** ```java @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; ``` --- ## T2: AuthService.register() — createdAt setzen **Beschreibung:** Setze `createdAt` auf `LocalDateTime.now()` in der `register()`-Methode. **Dateien:** - `eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AuthService.java` **Akzeptanz:** - [ ] `newUser.setCreatedAt(LocalDateTime.now())` vor `userRepository.save(newUser)` - [ ] Import für `LocalDateTime` vorhanden **Details:** Zeile ~77, nach `newUser.setVerificationToken(token)`: ```java newUser.setCreatedAt(LocalDateTime.now()); ``` --- ## T3: UserRepository — findAllByOrderByCreatedAtDesc() **Beschreibung:** Füge eine Spring Data Query-Methode hinzu, die alle Users absteigend nach Erstellungszeitpunkt sortiert. **Dateien:** - `eeg_backend/src/main/java/at/mueller/eeg/backend/iam/repository/UserRepository.java` **Akzeptanz:** - [ ] Methode `List findAllByOrderByCreatedAtDesc()` vorhanden - [ ] Kompiliert ohne Fehler **Details:** ```java List findAllByOrderByCreatedAtDesc(); ``` --- ## T4: MembershipRepository — findAllWithDetails() **Beschreibung:** Füge eine JPQL-Query hinzu, die alle Mitgliedschaften mit JOIN FETCH für MeteringPoint und EnergyCommunity zurückgibt. **Dateien:** - `eeg_backend/src/main/java/at/mueller/eeg/backend/community/repository/MembershipRepository.java` **Akzeptanz:** - [ ] `@Query` mit JOIN FETCH auf meteringPoint und energyCommunity - [ ] Kein WHERE-Clause (alle Status) - [ ] Kompiliert ohne Fehler **Details:** ```java @Query("SELECT m FROM Membership m JOIN FETCH m.meteringPoint mp JOIN FETCH m.energyCommunity ec") List findAllWithDetails(); ``` --- ## T5: MembershipService — getAllMemberships() **Beschreibung:** Neue Methode, die alle Mitgliedschaften lädt und als `PendingMembershipResponse`-Liste zurückgibt (mit User-Lookup). **Dateien:** - `eeg_backend/src/main/java/at/mueller/eeg/backend/community/service/MembershipService.java` **Akzeptanz:** - [ ] `@Transactional(readOnly = true)` Annotation - [ ] Nutzt `findAllWithDetails()` - [ ] Gleiche User-Lookup-Logik wie `getPendingMemberships()` (Batch-Fetch aller User-IDs) - [ ] Gibt `List` zurück **Details:** Kopiere die User-Lookup-Logik aus `getPendingMemberships()`, aber entferne den WHERE-Clause auf Status. Das `PendingMembershipResponse`-DTO muss um `validFrom`/`validTo` erweitert werden (siehe T9). --- ## T6: MembershipController — GET /admin/memberships **Beschreibung:** Neuer Admin-Endpoint für alle Mitgliedschaften. **Dateien:** - `eeg_backend/src/main/java/at/mueller/eeg/backend/community/api/MembershipController.java` **Akzeptanz:** - [ ] `@GetMapping("/admin/memberships")` mit `@PreAuthorize("hasRole('ADMIN')")` - [ ] Gibt `ResponseEntity>` zurück - [ ] Swagger-Annotationen (`@Operation`, `@ApiResponse`) **Details:** ```java @GetMapping("/admin/memberships") @PreAuthorize("hasRole('ADMIN')") @Operation(summary = "Lädt alle Mitgliedschaften", description = "Zeigt alle Mitgliedschaften aller Status.") public ResponseEntity> getAllMemberships() { return ResponseEntity.ok(membershipService.getAllMemberships()); } ``` --- ## T7: AdminIamService — getAllUsers() **Beschreibung:** Neue Methode, die alle Users sortiert nach Erstellungszeitpunkt zurückgibt. **Dateien:** - `eeg_backend/src/main/java/at/mueller/eeg/backend/iam/service/AdminIamService.java` **Akzeptanz:** - [ ] `@Transactional(readOnly = true)` Annotation - [ ] Nutzt `userRepository.findAllByOrderByCreatedAtDesc()` - [ ] Gibt `List` zurück **Details:** ```java @Transactional(readOnly = true) public List getAllUsers() { return userRepository.findAllByOrderByCreatedAtDesc(); } ``` --- ## T8: AdminIamController — GET /api/admin/users **Beschreibung:** Neuer Admin-Endpoint für alle Benutzer. **Dateien:** - `eeg_backend/src/main/java/at/mueller/eeg/backend/iam/api/AdminIamController.java` **Akzeptanz:** - [ ] `@GetMapping` auf Root-Pfad `/api/admin/users` - [ ] Nutzt `adminIamService.getAllUsers()` + `userMapper.mapToDto()` - [ ] Gibt `ResponseEntity>` zurück - [ ] Swagger-Annotationen **Details:** ```java @GetMapping @Operation(summary = "Lädt alle User", description = "Zeigt alle registrierten Benutzer.") public ResponseEntity> getAllUsers() { return ResponseEntity.ok(userMapper.mapToDto(adminIamService.getAllUsers())); } ``` --- ## T9: PendingMembershipResponse — validFrom/validTo erweitern **Beschreibung:** Erweitere das DTO-Record um `validFrom` und `validTo` für alle Mitgliedschaften. **Dateien:** - `eeg_backend/src/main/java/at/mueller/eeg/backend/community/api/dto/PendingMembershipResponse.java` **Akzeptanz:** - [ ] Record hat neue Felder `LocalDate validFrom`, `LocalDate validTo` - [ ] `getPendingMemberships()` in MembershipService setzt beide Felder (null für PENDING) - [ ] `getAllMemberships()` setzt beide Felder aus Entity **Details:** ```java record PendingMembershipResponse( UUID id, String userEmail, String firstName, String lastName, String atNumber, String communityName, Integer priorityLevel, String status, LocalDate validFrom, LocalDate validTo ) {} ``` Achtung: Da `PendingMembershipResponse` ein Java record ist, müssen ALLE Aufrufstellen aktualisiert werden, die dieses DTO erstellen. Insbesondere `getPendingMemberships()` in `MembershipService`. --- ## T10: Backend-Tests schreiben **Beschreibung:** Unit-Tests für die neuen Service-Methoden und Integrationstests für die neuen Endpoints. **Dateien:** - `eeg_backend/src/test/java/.../community/service/MembershipServiceTest.java` (neu oder erweitern) - `eeg_backend/src/test/java/.../iam/service/AdminIamServiceTest.java` (neu oder erweitern) **Akzeptanz:** - [ ] Test für `getAllMemberships()`: gibt alle Status zurück - [ ] Test für `getAllUsers()`: gibt alle Users sortiert zurück - [ ] Bestehende Tests weiterhin grün - [ ] `.\mvnw test -pl eeg_backend` erfolgreich --- ## T11: OpenAPI-Spec generieren + Frontend-API-Client regenerieren **Beschreibung:** Generiere die OpenAPI-Spec aus dem Backend und regeneriere den Frontend-API-Client. **Befehle:** ```bash .\mvnw test -pl eeg_backend -Dtest=OpenApiGeneratorTest cd eeg_frontend && npm run generate-api ``` **Akzeptanz:** - [ ] `eeg_frontend/openapi.yaml` enthält neuen Endpoint `/api/community/admin/memberships` - [ ] `eeg_frontend/openapi.yaml` enthält neuen Endpoint `/api/admin/users` (Root) - [ ] Generierter `AdminIAMService` enthält `getAllUsers()` - [ ] Keine Type-Errors im Frontend --- ## T12: Frontend MembershipService — getAllMemberships() **Beschreibung:** Neue Methode im Frontend MembershipService. **Dateien:** - `eeg_frontend/src/app/services/membership.ts` **Akzeptanz:** - [ ] `getAllMemberships(): Observable` vorhanden - [ ] Nutzt `GET /api/community/admin/memberships` **Details:** ```typescript getAllMemberships(): Observable { return this.http.get(`${this.apiUrl}/admin/memberships`); } ``` --- ## T13: AdminMembershipsComponent — Tab-Navigation + Tabelle **Beschreibung:** Erweitere die Admin-Mitgliedschafts-Seite mit Tab-Navigation und Tabellen-Ansicht. **Dateien:** - `eeg_frontend/src/app/pages/admin-memberships/admin-memberships.ts` - `eeg_frontend/src/app/pages/admin-memberships/admin-memberships.html` **Akzeptanz:** - [ ] Tab-Leiste: "Ausstehende Anträge" | "Aktive Mitgliedschaften" | "Alle" - [ ] Signal `activeTab: signal<'pending' | 'active' | 'all'>('pending')` - [ ] Signal `allMemberships: signal([])` - [ ] `loadAllMemberships()` lädt alle Daten beim Wechsel auf Tab "Aktive" oder "Alle" - [ ] Tab "Ausstehende": Bestehende Karten-Ansicht mit Genehmigen/Ablehnen - [ ] Tab "Aktive" + "Alle": Tabellen-Ansicht (Name, Email, Community, Zählpunkt, Priorität, Status) - [ ] Nur PENDING zeigt Aktions-Buttons - [ ] Hilfsfunktion `getStatusLabel(status: string): string` für deutsche Anzeige - [ ] Loading-Zustand für Tab-Wechsel **Details:** - Client-seitige Filterung: Beim Laden werden alle Mitgliedschaften in `allMemberships` gespeichert - Tab "Ausstehende" filtert nach `status === 'PENDING'` - Tab "Aktive" filtert nach `status === 'ACTIVE'` - Tab "Alle" zeigt alle - Tabellen-Header: Benutzer | Email | Gemeinschaft | Zählpunkt | Priorität | Status --- ## T14: AdminUserApprovalComponent — Tab-Navigation + Tabelle **Beschreibung:** Erweitere die Admin-Benutzerverwaltung mit Tab-Navigation und Tabelle für alle Users. **Dateien:** - `eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.ts` - `eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.html` **Akzeptanz:** - [ ] Tab-Leiste: "Ausstehende Anträge" | "Alle Benutzer" - [ ] Signal `activeTab: signal<'pending' | 'all'>('pending')` - [ ] Signal `allUsers: signal([])` - [ ] `loadAllUsers()` nutzt generierten `AdminIAMService.getAllUsers()` - [ ] Tab "Ausstehende": Bestehende Tabelle mit Freigeben/Ablehnen - [ ] Tab "Alle": Tabelle mit Name, Email, Status, Rolle (keine Aktions-Buttons) - [ ] Status-Badge: PENDING=gelb, APPROVED=grün, REJECTED=rot - [ ] Hilfsfunktion `getStatusLabel(status: string): string` - [ ] Bug fix: Doppelte "Status"-Spalten-Überschrift beheben (Zeile 32-33 in aktuellem Template) **Details:** - Tab "Ausstehende" lädt weiterhin `getPendingUsers()` (nur verifizierte PENDING-User) - Tab "Alle" lädt `getAllUsers()` beim ersten Wechsel - Status-Badges: `bg-yellow-100 text-yellow-800` (PENDING), `bg-green-100 text-green-800` (APPROVED), `bg-red-100 text-red-800` (REJECTED) --- ## T15: Integrations-Test — UI manuell prüfen **Beschreibung:** Manuelle Überprüfung der Admin-Seiten im Browser. **Akzeptanz:** - [ ] Backend startet ohne Fehler - [ ] `GET /api/community/admin/memberships` gibt alle Mitgliedschaften zurück - [ ] `GET /api/admin/users` gibt alle Benutzer zurück - [ ] Admin öffnet Mitgliedschaften → sieht Tabs "Ausstehende | Aktive | Alle" - [ ] Admin öffnet Benutzerverwaltung → sieht Tabs "Ausstehende | Alle" - [ ] Tab-Wechsel funktioniert sofort (kein Ladevorgang nötig außer beim ersten Laden) - [ ] Genehmigen/Ablehnen funktioniert weiterhin auf dem "Ausstehende"-Tab - [ ] Status-Badges werden korrekt angezeigt - [ ] `.\mvnw test -pl eeg_backend` alle Tests grün