eeg_portal/docs/compose/plans/implementiere-die-erweiterung-der-admin-bersichten-im-eeg-po.md
Bernhard Müller cfc32b3a7d chore: cleanup test data and add missing test files
- Add TariffInviteServiceTest, GlobalExceptionHandlerTest, setup-test.ts
- Add compose plans/specs docs
- Extend .gitignore with *.xlsx, *.ps1, *.http, *.py, *.docx
- Remove stale test data files and scripts from working tree
2026-07-24 11:48:56 +02:00

345 lines
12 KiB
Markdown

# 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<User> findAllByOrderByCreatedAtDesc()` vorhanden
- [ ] Kompiliert ohne Fehler
**Details:**
```java
List<User> 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<Membership> 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<PendingMembershipResponse>` 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<List<PendingMembershipResponse>>` 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<List<PendingMembershipResponse>> 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<User>` zurück
**Details:**
```java
@Transactional(readOnly = true)
public List<User> 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<List<UserProfileResponse>>` zurück
- [ ] Swagger-Annotationen
**Details:**
```java
@GetMapping
@Operation(summary = "Lädt alle User", description = "Zeigt alle registrierten Benutzer.")
public ResponseEntity<List<UserProfileResponse>> 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<PendingMembershipResponse[]>` vorhanden
- [ ] Nutzt `GET /api/community/admin/memberships`
**Details:**
```typescript
getAllMemberships(): Observable<PendingMembershipResponse[]> {
return this.http.get<PendingMembershipResponse[]>(`${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<PendingMembershipResponse[]>([])`
- [ ] `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<UserProfileResponse[]>([])`
- [ ] `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