- 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
345 lines
12 KiB
Markdown
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
|