Compare commits
3 Commits
c2f85cca57
...
7a5580e57d
| Author | SHA1 | Date |
|---|---|---|
|
|
7a5580e57d | |
|
|
51b8c2dbd4 | |
|
|
f9e72d095b |
|
|
@ -1,15 +1,15 @@
|
||||||
1. Allgemeine Rahmenbedingungen
|
1. Allgemeine Rahmenbedingungen
|
||||||
Die Finanzverwaltung wird als Modul / Erweiterung einer bestehenden Software umgesetzt.
|
Die Finanzverwaltung wird als Modul / Erweiterung der bestehenden Software umgesetzt.
|
||||||
|
|
||||||
Eine Benutzerverwaltung existiert bereits und wird genutzt.
|
Eine Benutzerverwaltung existiert bereits und wird genutzt.
|
||||||
|
|
||||||
Der Kegelverein ist kein eingetragener Verein, es bestehen keine steuerlichen oder rechtlichen Sonderanforderungen.
|
Der Kegelverein ist kein eingetragener Verein, es bestehen keine steuerlichen oder rechtlichen Sonderanforderungen.
|
||||||
|
|
||||||
Es gibt ein Vereinskonto und eine Währung (EUR).
|
Es gibt ein Vereinskonto und eine Währung (EUR) pro angelegtem Verin.
|
||||||
|
|
||||||
2. Benutzer- und Rollenmodell
|
2. Benutzer- und Rollenmodell
|
||||||
2.1 Rollen
|
2.1 Rollen
|
||||||
Es wird eine zusätzliche Rolle für Finanzdaten integriert (z. B. „Kassenwart“, „Finanzverwaltung“).
|
Es wird eine zusätzliche Rolle für Finanzdaten integriert (z. B. „Kassenwart“ oder „Finanzverwaltung“).
|
||||||
|
|
||||||
Nur Benutzer mit dieser Rolle dürfen:
|
Nur Benutzer mit dieser Rolle dürfen:
|
||||||
|
|
||||||
|
|
@ -23,275 +23,132 @@ Finanzdaten ändern oder löschen
|
||||||
|
|
||||||
Berichte exportieren
|
Berichte exportieren
|
||||||
|
|
||||||
2.2 Zugriffsrechte
|
## 2.2 Zugriffsrechte
|
||||||
Benutzer ohne Finanzrolle haben keinen Zugriff auf Finanzdaten.
|
Benutzer ohne Finanzrolle haben Änderungsrechte auf Finanzdaten.
|
||||||
|
|
||||||
Leserechte und Schreibrechte müssen klar getrennt umsetzbar sein (falls von der bestehenden Software unterstützt).
|
Leserechte und Schreibrechte müssen klar getrennt umsetzbar sein.
|
||||||
|
|
||||||
3. Kategorienverwaltung
|
# 3. Kategorienverwaltung
|
||||||
3.1 Kategorien für Einnahmen und Ausgaben
|
## 3.1 Kategorien für Einnahmen und Ausgaben
|
||||||
Einnahmen und Ausgaben müssen kategorisiert werden.
|
Einnahmen und Ausgaben müssen kategorisiert werden.
|
||||||
|
|
||||||
Kategorien sind:
|
Kategorien sind:
|
||||||
|
- frei definierbar
|
||||||
|
- beliebig erweiterbar
|
||||||
|
- editierbar (Name ändern)
|
||||||
|
- deaktivierbar, ohne bestehende Buchungen zu verändern
|
||||||
|
|
||||||
frei definierbar
|
## 3.2 Kategorietypen
|
||||||
|
|
||||||
beliebig erweiterbar
|
|
||||||
|
|
||||||
editierbar (Name ändern)
|
|
||||||
|
|
||||||
deaktivierbar, ohne bestehende Buchungen zu verändern
|
|
||||||
|
|
||||||
3.2 Kategorietypen
|
|
||||||
Es muss mindestens eine Unterscheidung geben zwischen:
|
Es muss mindestens eine Unterscheidung geben zwischen:
|
||||||
|
- Einnahmen-Kategorien
|
||||||
|
- Ausgaben-Kategorien
|
||||||
|
- Kategorien sollten für bessere Usability zusätzlich mit einer Farbe und einer Gruppierung oder Icons versehbar sein
|
||||||
|
|
||||||
Einnahmen-Kategorien
|
# 4. Buchungen (Kassenbuch)
|
||||||
|
## 4.1 Erfassung von Einnahmen und Ausgaben
|
||||||
Ausgaben-Kategorien
|
|
||||||
|
|
||||||
4. Buchungen (Kassenbuch)
|
|
||||||
4.1 Erfassung von Einnahmen und Ausgaben
|
|
||||||
Für jede Buchung müssen folgende Felder vorhanden sein:
|
Für jede Buchung müssen folgende Felder vorhanden sein:
|
||||||
|
|
||||||
Buchungsart: Einnahme / Ausgabe
|
- Buchungsart: Einnahme / Ausgabe
|
||||||
|
- Betrag (positiv, in EUR)
|
||||||
|
- Kategorie
|
||||||
|
- Buchungsdatum (frei wählbar, nicht zwingend aktuelles Datum)
|
||||||
|
- Optionaler Kommentar / Beschreibung
|
||||||
|
- Optionaler externer Belegverweis (z. B. Belegnummer oder Text)
|
||||||
|
|
||||||
Betrag (positiv, in EUR)
|
## 4.2 Belege
|
||||||
|
|
||||||
Kategorie
|
|
||||||
|
|
||||||
Buchungsdatum (frei wählbar, nicht zwingend aktuelles Datum)
|
|
||||||
|
|
||||||
Optionaler Kommentar / Beschreibung
|
|
||||||
|
|
||||||
Optionaler externer Belegverweis (z. B. Belegnummer oder Text)
|
|
||||||
|
|
||||||
4.2 Belege
|
|
||||||
Kein Upload von Dokumenten
|
Kein Upload von Dokumenten
|
||||||
|
|
||||||
Stattdessen:
|
Stattdessen:
|
||||||
|
- Freitextfeld oder Referenz zur Verknüpfung mit extern abgelegten Belegen
|
||||||
|
|
||||||
Freitextfeld oder Referenz zur Verknüpfung mit extern abgelegten Belegen
|
# 5. Kontostand und Kontenabgleich
|
||||||
|
## 5.1 Kontostand
|
||||||
5. Kontostand und Kontenabgleich
|
|
||||||
5.1 Kontostand
|
|
||||||
Die Software führt einen internen Kontostand auf Basis der erfassten Buchungen.
|
Die Software führt einen internen Kontostand auf Basis der erfassten Buchungen.
|
||||||
|
|
||||||
Es existiert genau ein Konto.
|
Es existiert genau ein Konto pro Verein.
|
||||||
|
|
||||||
5.2 Manueller Abgleich
|
## 5.2 Manueller Abgleich
|
||||||
Der Abgleich mit dem realen Bankkonto erfolgt manuell.
|
Der Abgleich mit dem realen Bankkonto erfolgt manuell.
|
||||||
|
|
||||||
Abweichungen können durch Korrekturbuchungen ausgeglichen werden.
|
Abweichungen können durch Korrekturbuchungen ausgeglichen werden.
|
||||||
|
|
||||||
Korrekturbuchungen:
|
Korrekturbuchungen:
|
||||||
|
- sind normale Buchungen
|
||||||
|
- müssen eindeutig als solche erkennbar sein (z. B. spezielle Kategorie oder Kennzeichnung)
|
||||||
|
- Dafür werden doe Kategorien "Korrekturbuchung" und "Saldoanpassung" mit ausgeliefert, die nicht löschbar ist
|
||||||
|
|
||||||
sind normale Buchungen
|
# 6. Auswertungen und Berichte
|
||||||
|
## 6.1 Standardberichte
|
||||||
müssen eindeutig als solche erkennbar sein (z. B. spezielle Kategorie oder Kennzeichnung)
|
|
||||||
|
|
||||||
6. Auswertungen und Berichte
|
|
||||||
6.1 Standardberichte
|
|
||||||
Folgende Berichte müssen verfügbar sein:
|
Folgende Berichte müssen verfügbar sein:
|
||||||
|
- Monatsübersicht
|
||||||
|
- Jahresübersicht
|
||||||
|
|
||||||
Monatsübersicht
|
## 6.2 Inhalt der Berichte
|
||||||
|
- Anfangs- und Endsaldo
|
||||||
|
- Summe der Einnahmen
|
||||||
|
- Summe der Ausgaben
|
||||||
|
- Aufschlüsselung nach Kategorien
|
||||||
|
- Optionale Detailansicht aller Buchungen im Zeitraum
|
||||||
|
|
||||||
Jahresübersicht
|
## 6.3 Export
|
||||||
|
|
||||||
6.2 Inhalt der Berichte
|
|
||||||
Anfangs- und Endsaldo
|
|
||||||
|
|
||||||
Summe der Einnahmen
|
|
||||||
|
|
||||||
Summe der Ausgaben
|
|
||||||
|
|
||||||
Aufschlüsselung nach Kategorien
|
|
||||||
|
|
||||||
Optionale Detailansicht aller Buchungen im Zeitraum
|
|
||||||
|
|
||||||
6.3 Export
|
|
||||||
Berichte müssen exportierbar sein als:
|
Berichte müssen exportierbar sein als:
|
||||||
|
- Excel (z. B. XLSX)
|
||||||
|
- PDF
|
||||||
|
|
||||||
Excel (z. B. XLSX)
|
# 7. Änderungsprotokoll (Audit-Log)
|
||||||
|
## 7.1 Protokollierung
|
||||||
PDF
|
|
||||||
|
|
||||||
7. Änderungsprotokoll (Audit-Log)
|
|
||||||
7.1 Protokollierung
|
|
||||||
Alle Änderungen an Finanzdaten müssen protokolliert werden:
|
Alle Änderungen an Finanzdaten müssen protokolliert werden:
|
||||||
|
- Neuanlage von Buchungen
|
||||||
|
- Änderungen an Buchungen
|
||||||
|
- Löschungen von Buchungen
|
||||||
|
Hierfür kann die BaseEntity genutzt werden. Es kein zusätzlicher Aufwand erforderlich.
|
||||||
|
|
||||||
Neuanlage von Buchungen
|
|
||||||
|
|
||||||
Änderungen an Buchungen
|
# 8. Bedienbarkeit und Usability
|
||||||
|
|
||||||
Löschungen von Buchungen
|
|
||||||
|
|
||||||
Änderungen an Kategorien
|
|
||||||
|
|
||||||
7.2 Protokollinhalt
|
|
||||||
Das Protokoll enthält mindestens:
|
|
||||||
|
|
||||||
Datum und Uhrzeit der Änderung
|
|
||||||
|
|
||||||
Benutzer
|
|
||||||
|
|
||||||
Art der Änderung (Anlage / Änderung / Löschung)
|
|
||||||
|
|
||||||
Alte und neue Werte (soweit sinnvoll darstellbar)
|
|
||||||
|
|
||||||
7.3 Einsicht
|
|
||||||
Das Änderungsprotokoll ist für berechtigte Benutzer einsehbar.
|
|
||||||
|
|
||||||
8. Bedienbarkeit und Usability
|
|
||||||
Zielgruppe sind softwareerfahrene Benutzer.
|
Zielgruppe sind softwareerfahrene Benutzer.
|
||||||
|
|
||||||
Es wird keine Benutzeranleitung benötigt.
|
Es wird kleine Benutzeranleitung benötigt.
|
||||||
|
|
||||||
Fokus liegt auf:
|
Fokus liegt auf:
|
||||||
|
- klaren Masken
|
||||||
klaren Masken
|
- übersichtlichen Tabellen
|
||||||
|
- schneller Datenerfassung
|
||||||
übersichtlichen Tabellen
|
|
||||||
|
|
||||||
schneller Datenerfassung
|
|
||||||
|
|
||||||
Konsistenz mit der bestehenden Software-Oberfläche ist wünschenswert.
|
Konsistenz mit der bestehenden Software-Oberfläche ist wünschenswert.
|
||||||
|
|
||||||
9. Technische und funktionale Abgrenzungen (Nicht-Ziele)
|
# 9. Technische und funktionale Abgrenzungen (Nicht-Ziele)
|
||||||
Folgende Funktionen sind nicht Bestandteil der ersten Version:
|
Folgende Funktionen sind nicht Bestandteil der ersten Version:
|
||||||
|
|
||||||
Automatische Zahlungserinnerungen
|
- Automatische Zahlungserinnerungen
|
||||||
|
- Import von Bankumsätzen
|
||||||
|
- Schnittstellen zu anderen Systemen
|
||||||
|
- Dokumenten-Uploads
|
||||||
|
- Mehrere Konten oder Währungen
|
||||||
|
- Steuerliche oder rechtliche Auswertungen
|
||||||
|
|
||||||
Import von Bankumsätzen
|
# 10. Erweiterbarkeit (optional, aber empfehlenswert)
|
||||||
|
|
||||||
Schnittstellen zu anderen Systemen
|
|
||||||
|
|
||||||
Dokumenten-Uploads
|
|
||||||
|
|
||||||
Mehrere Konten oder Währungen
|
|
||||||
|
|
||||||
Steuerliche oder rechtliche Auswertungen
|
|
||||||
|
|
||||||
10. Erweiterbarkeit (optional, aber empfehlenswert)
|
|
||||||
Die Architektur sollte so gestaltet sein, dass spätere Erweiterungen möglich sind, z. B.:
|
Die Architektur sollte so gestaltet sein, dass spätere Erweiterungen möglich sind, z. B.:
|
||||||
|
|
||||||
Automatischer Kontoimport
|
- Automatischer Kontoimport
|
||||||
|
- Mehrere Konten
|
||||||
|
- Erinnerungen
|
||||||
|
- Schnittstellen
|
||||||
|
|
||||||
Mehrere Konten
|
# 11. Integration in bestehen Anwendung
|
||||||
|
|
||||||
Erinnerungen
|
## 11.1
|
||||||
|
Abschluss eines Spieltages erzeugt neue Buchung
|
||||||
|
- beim Abschluss eines Spieltages wird die Strafen-Summe pro Spieler und Tag dem Kassenbuch hinzugefügt
|
||||||
|
- Dafür wird eine Kategorie "Spielstrafe" mit ausgeliefert, die nicht löschbar ist
|
||||||
|
- Die Strafen die die Buchung auslösen werden damit als erledigt gekennzeichnet
|
||||||
|
|
||||||
Schnittstellen
|
## 11.2
|
||||||
|
Mitgliedsbeiträge
|
||||||
|
- Die Vereineinstellungen werden um einen monatlichen Mitgliedsbeitrag erweitert
|
||||||
|
- Dafür wird eine Kategorie "Mitgliedsbeitrag" mit ausgeliefert, die nicht löschbar ist
|
||||||
|
- Die Mitgliedsbeiträge können über einen Button für einen Montag ermittelt und ins Kassenbuch aufgenommen werden
|
||||||
|
|
||||||
Wenn du möchtest, kann ich dir im nächsten Schritt z. B.
|
|
||||||
|
|
||||||
ein Datenmodell (Tabellen / Entitäten)
|
|
||||||
|
|
||||||
User Stories
|
|
||||||
|
|
||||||
oder eine Minimal-Feature-Liste für ein MVP
|
|
||||||
ableiten – sag einfach Bescheid.
|
|
||||||
|
|
||||||
Du:
|
|
||||||
ja, mach das
|
|
||||||
ChatGPT:
|
|
||||||
Perfekt 👍
|
|
||||||
Dann liefere ich dir alle drei Punkte in sinnvoller Reihenfolge und aufeinander aufbauend:
|
|
||||||
|
|
||||||
Datenmodell (Entitäten & Felder)
|
|
||||||
|
|
||||||
User Stories
|
|
||||||
|
|
||||||
MVP-Feature-Liste (klar abgegrenzt)
|
|
||||||
|
|
||||||
So kannst du direkt mit Design oder Implementierung starten.
|
|
||||||
|
|
||||||
1. Datenmodell (fachlich / logisch)
|
|
||||||
1.1 Benutzer
|
|
||||||
Existiert bereits – wird hier nur referenziert
|
|
||||||
|
|
||||||
User
|
|
||||||
|
|
||||||
user_id (PK)
|
|
||||||
|
|
||||||
name
|
|
||||||
|
|
||||||
… (bestehende Felder)
|
|
||||||
|
|
||||||
rolle_finanz (Boolean oder Rollenreferenz)
|
|
||||||
|
|
||||||
1.2 Kategorien
|
|
||||||
Kategorie
|
|
||||||
|
|
||||||
kategorie_id (PK)
|
|
||||||
|
|
||||||
name
|
|
||||||
|
|
||||||
typ (EINNAHME | AUSGABE)
|
|
||||||
|
|
||||||
aktiv (Boolean)
|
|
||||||
|
|
||||||
erstellt_am
|
|
||||||
|
|
||||||
erstellt_von (user_id)
|
|
||||||
|
|
||||||
1.3 Buchungen (Kassenbuch)
|
|
||||||
Buchung
|
|
||||||
|
|
||||||
buchung_id (PK)
|
|
||||||
|
|
||||||
buchungsart (EINNAHME | AUSGABE)
|
|
||||||
|
|
||||||
betrag (Decimal, > 0)
|
|
||||||
|
|
||||||
buchungsdatum (Date)
|
|
||||||
|
|
||||||
kategorie_id (FK)
|
|
||||||
|
|
||||||
kommentar (Text, optional)
|
|
||||||
|
|
||||||
belegreferenz (Text, optional)
|
|
||||||
|
|
||||||
ist_korrekturbuchung (Boolean)
|
|
||||||
|
|
||||||
erstellt_am (Timestamp)
|
|
||||||
|
|
||||||
erstellt_von (user_id)
|
|
||||||
|
|
||||||
geändert_am (Timestamp, optional)
|
|
||||||
|
|
||||||
geändert_von (user_id, optional)
|
|
||||||
|
|
||||||
gelöscht (Boolean, Soft Delete empfohlen)
|
|
||||||
|
|
||||||
1.4 Änderungsprotokoll (Audit-Log)
|
|
||||||
Finanz_Audit_Log
|
|
||||||
|
|
||||||
log_id (PK)
|
|
||||||
|
|
||||||
entität (BUCHUNG | KATEGORIE)
|
|
||||||
|
|
||||||
entität_id
|
|
||||||
|
|
||||||
aktion (CREATE | UPDATE | DELETE)
|
|
||||||
|
|
||||||
feldname (optional, bei UPDATE)
|
|
||||||
|
|
||||||
alter_wert (Text)
|
|
||||||
|
|
||||||
neuer_wert (Text)
|
|
||||||
|
|
||||||
zeitpunkt (Timestamp)
|
|
||||||
|
|
||||||
benutzer_id (user_id)
|
|
||||||
|
|
||||||
Empfehlung: bei UPDATE pro Feld ein Logeintrag
|
|
||||||
|
|
||||||
1.5 Abgeleitete Werte (kein persistentes Objekt)
|
|
||||||
Kontostand
|
|
||||||
|
|
||||||
wird dynamisch berechnet:
|
|
||||||
|
|
||||||
Summe(Einnahmen) – Summe(Ausgaben)
|
|
||||||
2. User Stories (fachlich, umsetzungsnah)
|
2. User Stories (fachlich, umsetzungsnah)
|
||||||
Kategorien
|
Kategorien
|
||||||
Als Finanznutzer möchte ich Kategorien für Einnahmen und Ausgaben anlegen,
|
Als Finanznutzer möchte ich Kategorien für Einnahmen und Ausgaben anlegen,
|
||||||
|
|
@ -330,51 +187,3 @@ um sie weiterzugeben oder zu archivieren.
|
||||||
Sicherheit & Nachvollziehbarkeit
|
Sicherheit & Nachvollziehbarkeit
|
||||||
Als Verein möchten wir, dass alle Änderungen an Finanzdaten protokolliert werden,
|
Als Verein möchten wir, dass alle Änderungen an Finanzdaten protokolliert werden,
|
||||||
damit Transparenz und Nachvollziehbarkeit gewährleistet sind.
|
damit Transparenz und Nachvollziehbarkeit gewährleistet sind.
|
||||||
|
|
||||||
3. MVP – Minimal Viable Product
|
|
||||||
3.1 Muss-Funktionen (Version 1.0)
|
|
||||||
✅ Pflicht
|
|
||||||
|
|
||||||
Finanzrolle integrieren
|
|
||||||
|
|
||||||
Kategorien anlegen / bearbeiten / deaktivieren
|
|
||||||
|
|
||||||
Einnahmen & Ausgaben erfassen
|
|
||||||
|
|
||||||
Buchungen bearbeiten & (soft) löschen
|
|
||||||
|
|
||||||
Korrekturbuchungen
|
|
||||||
|
|
||||||
Kontostandberechnung
|
|
||||||
|
|
||||||
Monats- & Jahresübersichten
|
|
||||||
|
|
||||||
Export als Excel & PDF
|
|
||||||
|
|
||||||
Vollständiges Änderungsprotokoll
|
|
||||||
|
|
||||||
3.2 Sollte-Funktionen (Version 1.1)
|
|
||||||
🔶 Optional, aber sinnvoll
|
|
||||||
|
|
||||||
Filter (Zeitraum, Kategorie)
|
|
||||||
|
|
||||||
Summen pro Kategorie
|
|
||||||
|
|
||||||
Kennzeichnung auffälliger Buchungen (z. B. hohe Beträge)
|
|
||||||
|
|
||||||
Schnellanlage über Standardkategorien
|
|
||||||
|
|
||||||
3.3 Explizit ausgeschlossen (vorerst)
|
|
||||||
❌ Nicht Teil des MVP
|
|
||||||
|
|
||||||
Bankimport
|
|
||||||
|
|
||||||
Mehrere Konten
|
|
||||||
|
|
||||||
Mehrere Währungen
|
|
||||||
|
|
||||||
Dokumentenupload
|
|
||||||
|
|
||||||
Automatische Erinnerungen
|
|
||||||
|
|
||||||
Externe Schnittstellen
|
|
||||||
|
|
@ -613,6 +613,9 @@ public class UserService : IUserService
|
||||||
|
|
||||||
await _appDb.SaveChangesAsync(ct);
|
await _appDb.SaveChangesAsync(ct);
|
||||||
|
|
||||||
|
// Assign default Viewer role to new member
|
||||||
|
await AssignClubRoleAsync(userProfileId, clubId, "Viewer", approvedById, ct);
|
||||||
|
|
||||||
// Send notification to user
|
// Send notification to user
|
||||||
var identityUser = await _userManager.FindByIdAsync(membership.UserProfile.IdentityUserId.ToString());
|
var identityUser = await _userManager.FindByIdAsync(membership.UserProfile.IdentityUserId.ToString());
|
||||||
if (identityUser?.Email != null)
|
if (identityUser?.Email != null)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,14 @@
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private HttpContext HttpContext { get; set; } = default!;
|
private HttpContext HttpContext { get; set; } = default!;
|
||||||
|
|
||||||
private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
|
private IComponentRenderMode? RenderModeForPage
|
||||||
? new InteractiveServerRenderMode(prerender: false)
|
{
|
||||||
: new InteractiveServerRenderMode(prerender: true);
|
get
|
||||||
|
{
|
||||||
|
var path = HttpContext.Request.Path;
|
||||||
|
var disablePrerender = path.StartsWithSegments("/Account")
|
||||||
|
|| path.StartsWithSegments("/settings");
|
||||||
|
return new InteractiveServerRenderMode(prerender: !disablePrerender);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
@using Koogle.Application.DTOs
|
||||||
|
@using Koogle.Application.Interfaces
|
||||||
|
@using Koogle.Domain.Enums
|
||||||
|
|
||||||
|
@inject IUserService UserService
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudText Typo="Typo.subtitle1" Class="mb-3">Rollen fuer @User.DisplayName</MudText>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-3" Elevation="1">
|
||||||
|
<MudStack Row="true" Spacing="2" Wrap="Wrap.Wrap">
|
||||||
|
@foreach (var role in _availableRoles)
|
||||||
|
{
|
||||||
|
var hasRole = _currentRoles.Contains(role);
|
||||||
|
<MudChip T="string"
|
||||||
|
Color="@(hasRole ? Color.Primary : Color.Default)"
|
||||||
|
Variant="@(hasRole ? Variant.Filled : Variant.Outlined)"
|
||||||
|
OnClick="@(() => ToggleRole(role, hasRole))"
|
||||||
|
Disabled="_isProcessing">
|
||||||
|
@GetRoleDisplayName(role)
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
<MudDivider Class="my-4" />
|
||||||
|
|
||||||
|
<MudButton Size="Size.Small"
|
||||||
|
Color="Color.Error"
|
||||||
|
Variant="Variant.Text"
|
||||||
|
StartIcon="@Icons.Material.Filled.RemoveCircle"
|
||||||
|
OnClick="RemoveFromClub"
|
||||||
|
Disabled="_isProcessing">
|
||||||
|
Aus Verein entfernen
|
||||||
|
</MudButton>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton OnClick="Close">Schliessen</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||||
|
|
||||||
|
[Parameter] public UserDto User { get; set; } = null!;
|
||||||
|
[Parameter] public Guid ClubId { get; set; }
|
||||||
|
[Parameter] public Guid CurrentUserProfileId { get; set; }
|
||||||
|
|
||||||
|
// Available roles for club-admin (no SuperAdmin)
|
||||||
|
private readonly string[] _availableRoles = { UserRole.Viewer, UserRole.Editor, UserRole.Admin };
|
||||||
|
private List<string> _currentRoles = new();
|
||||||
|
private bool _isProcessing;
|
||||||
|
private bool _hasChanges;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
var membership = User.ClubMemberships.FirstOrDefault(m => m.ClubId == ClubId);
|
||||||
|
_currentRoles = membership?.Roles.ToList() ?? new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRoleDisplayName(string role) => role switch
|
||||||
|
{
|
||||||
|
UserRole.Viewer => "Betrachter",
|
||||||
|
UserRole.Editor => "Bearbeiter",
|
||||||
|
UserRole.Admin => "Administrator",
|
||||||
|
_ => role
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task ToggleRole(string role, bool currentlyHasRole)
|
||||||
|
{
|
||||||
|
_isProcessing = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
if (currentlyHasRole)
|
||||||
|
{
|
||||||
|
success = await UserService.RemoveClubRoleAsync(User.ProfileId, ClubId, role);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = await UserService.AssignClubRoleAsync(User.ProfileId, ClubId, role, CurrentUserProfileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
if (currentlyHasRole)
|
||||||
|
_currentRoles.Remove(role);
|
||||||
|
else
|
||||||
|
_currentRoles.Add(role);
|
||||||
|
|
||||||
|
_hasChanges = true;
|
||||||
|
Snackbar.Add(currentlyHasRole ? "Rolle entfernt" : "Rolle hinzugefuegt", Severity.Success);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("Fehler beim Aendern der Rolle", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isProcessing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveFromClub()
|
||||||
|
{
|
||||||
|
_isProcessing = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var success = await UserService.RemoveUserFromClubAsync(User.ProfileId, ClubId);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
_hasChanges = true;
|
||||||
|
Snackbar.Add("Aus Verein entfernt", Severity.Success);
|
||||||
|
MudDialog.Close(DialogResult.Ok(_hasChanges));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("Fehler beim Entfernen", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isProcessing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Close() => MudDialog.Close(DialogResult.Ok(_hasChanges));
|
||||||
|
}
|
||||||
|
|
@ -144,6 +144,15 @@
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudCard>
|
</MudCard>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudCard Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;">
|
||||||
|
<MudStack AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AccountBalance" Color="Color.Warning" Size="Size.Large" />
|
||||||
|
<MudText Typo="Typo.subtitle1" Class="font-weight-bold" Align="Align.Center">Kassenbuch</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Align="Align.Center" Color="Color.Secondary">autom. Buchungen, Kontoabgleich u.v.m wird euer Kassenwart lieben</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudCard>
|
||||||
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -155,7 +164,7 @@
|
||||||
<MudStack Spacing="2">
|
<MudStack Spacing="2">
|
||||||
<MudStack Row AlignItems="AlignItems.Center" Spacing="2">
|
<MudStack Row AlignItems="AlignItems.Center" Spacing="2">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" />
|
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" />
|
||||||
<MudText>Rollenbasierte Berechtigungen (Viewer/Editor/Admin)</MudText>
|
<MudText>Rollenbasierte Berechtigungen (Viewer/Editor/Schatzmeister/Admin)</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudStack Row AlignItems="AlignItems.Center" Spacing="2">
|
<MudStack Row AlignItems="AlignItems.Center" Spacing="2">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" />
|
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" />
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,61 @@ else
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
</MudCard>
|
</MudCard>
|
||||||
</MudTabPanel>
|
</MudTabPanel>
|
||||||
|
|
||||||
|
<MudTabPanel Text="Mitglieder" BadgeData="@(_clubMembers.Count)" BadgeColor="Color.Primary">
|
||||||
|
<MudCard Class="mt-4">
|
||||||
|
<MudCardContent>
|
||||||
|
@if (_isLoadingMembers)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Indeterminate="true" Size="Size.Small" />
|
||||||
|
}
|
||||||
|
else if (!_clubMembers.Any())
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info">Keine Mitglieder vorhanden.</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTable Items="_clubMembers" Dense="true" Hover="true">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Name</MudTh>
|
||||||
|
<MudTh>E-Mail</MudTh>
|
||||||
|
<MudTh>Rollen</MudTh>
|
||||||
|
<MudTh>Aktionen</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd DataLabel="Name">@context.DisplayName</MudTd>
|
||||||
|
<MudTd DataLabel="E-Mail">@context.Identity.Email</MudTd>
|
||||||
|
<MudTd DataLabel="Rollen">
|
||||||
|
@{
|
||||||
|
var membership = context.ClubMemberships.FirstOrDefault(m => m.ClubId == CurrentClubContext.ClubId);
|
||||||
|
if (membership != null && membership.Roles.Any())
|
||||||
|
{
|
||||||
|
@foreach (var role in membership.Roles)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="GetRoleColor(role)" Class="mr-1">
|
||||||
|
@GetRoleDisplayName(role)
|
||||||
|
</MudChip>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Warning">Keine Rolle</MudText>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="Aktionen">
|
||||||
|
<MudTooltip Text="Rollen verwalten">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Security"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="@(() => OpenMemberRolesDialog(context))" />
|
||||||
|
</MudTooltip>
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
}
|
||||||
|
</MudCardContent>
|
||||||
|
</MudCard>
|
||||||
|
</MudTabPanel>
|
||||||
</MudTabs>
|
</MudTabs>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,9 +259,11 @@ else
|
||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
private bool _isLoadingInvitations = true;
|
private bool _isLoadingInvitations = true;
|
||||||
private bool _isLoadingPending = true;
|
private bool _isLoadingPending = true;
|
||||||
|
private bool _isLoadingMembers = true;
|
||||||
private int _activeTabIndex = 0;
|
private int _activeTabIndex = 0;
|
||||||
private List<ClubInvitationDto> _invitations = new();
|
private List<ClubInvitationDto> _invitations = new();
|
||||||
private List<PendingMembershipDto> _pendingMemberships = new();
|
private List<PendingMembershipDto> _pendingMemberships = new();
|
||||||
|
private List<UserDto> _clubMembers = new();
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
|
@ -217,19 +274,34 @@ else
|
||||||
private async Task LoadAllDataAsync()
|
private async Task LoadAllDataAsync()
|
||||||
{
|
{
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
_isLoadingInvitations = true;
|
||||||
|
_isLoadingPending = true;
|
||||||
|
_isLoadingMembers = true;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var clubId = CurrentClubContext.ClubId;
|
var clubId = CurrentClubContext.ClubId;
|
||||||
if (clubId != Guid.Empty)
|
if (clubId != Guid.Empty)
|
||||||
{
|
{
|
||||||
_club = await ClubService.GetByIdAsync(clubId);
|
_club = await ClubService.GetByIdAsync(clubId);
|
||||||
await LoadInvitationsAsync();
|
_isLoading = false;
|
||||||
await LoadPendingMembershipsAsync();
|
|
||||||
|
_invitations = (await ClubService.GetInvitationsByClubAsync(clubId)).ToList();
|
||||||
|
_isLoadingInvitations = false;
|
||||||
|
|
||||||
|
_pendingMemberships = (await UserService.GetPendingMembershipsAsync(clubId)).ToList();
|
||||||
|
_isLoadingPending = false;
|
||||||
|
|
||||||
|
_clubMembers = (await UserService.GetUsersByClubAsync(clubId)).ToList();
|
||||||
|
_isLoadingMembers = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
|
_isLoadingInvitations = false;
|
||||||
|
_isLoadingPending = false;
|
||||||
|
_isLoadingMembers = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,6 +339,23 @@ else
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoadMembersAsync()
|
||||||
|
{
|
||||||
|
_isLoadingMembers = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var clubId = CurrentClubContext.ClubId;
|
||||||
|
if (clubId != Guid.Empty)
|
||||||
|
{
|
||||||
|
_clubMembers = (await UserService.GetUsersByClubAsync(clubId)).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isLoadingMembers = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ClearError()
|
private void ClearError()
|
||||||
{
|
{
|
||||||
Dispatcher.Dispatch(new ClearClubErrorAction());
|
Dispatcher.Dispatch(new ClearClubErrorAction());
|
||||||
|
|
@ -461,4 +550,47 @@ else
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetRoleDisplayName(string role) => role switch
|
||||||
|
{
|
||||||
|
UserRole.Viewer => "Betrachter",
|
||||||
|
UserRole.Editor => "Bearbeiter",
|
||||||
|
UserRole.Admin => "Administrator",
|
||||||
|
_ => role
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Color GetRoleColor(string role) => role switch
|
||||||
|
{
|
||||||
|
UserRole.Admin => Color.Error,
|
||||||
|
UserRole.Editor => Color.Warning,
|
||||||
|
UserRole.Viewer => Color.Info,
|
||||||
|
_ => Color.Default
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task OpenMemberRolesDialog(UserDto user)
|
||||||
|
{
|
||||||
|
var currentUser = await UserService.GetCurrentUserAsync();
|
||||||
|
if (currentUser == null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("Nicht angemeldet", Severity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
{ "User", user },
|
||||||
|
{ "ClubId", CurrentClubContext.ClubId },
|
||||||
|
{ "CurrentUserProfileId", currentUser.ProfileId }
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small };
|
||||||
|
var dialog = await DialogService.ShowAsync<ClubMemberRolesDialog>("Rollen verwalten", parameters, options);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result != null && !result.Canceled && result.Data is bool hasChanges && hasChanges)
|
||||||
|
{
|
||||||
|
await LoadMembersAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Globalization;
|
||||||
using ApexCharts;
|
using ApexCharts;
|
||||||
using Fluxor;
|
using Fluxor;
|
||||||
using Fluxor.Blazor.Web.ReduxDevTools;
|
using Fluxor.Blazor.Web.ReduxDevTools;
|
||||||
|
|
@ -140,4 +141,8 @@ await BootstrapSeeder.SeedTriggersAsync(app.Services);
|
||||||
await DemoSeeder.SeedAsync(app.Services, app.Configuration, app.Environment);
|
await DemoSeeder.SeedAsync(app.Services, app.Configuration, app.Environment);
|
||||||
|
|
||||||
|
|
||||||
|
var culture = new CultureInfo("de-DE");
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = culture;
|
||||||
|
CultureInfo.DefaultThreadCurrentUICulture = culture;
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue