Compare commits
No commits in common. "7a5580e57d3753c52d7ccc290cbb4815a907d6b3" and "c2f85cca575813c3dd2ccb01cfcd201d9cd00a6a" have entirely different histories.
7a5580e57d
...
c2f85cca57
|
|
@ -1,15 +1,15 @@
|
||||||
1. Allgemeine Rahmenbedingungen
|
1. Allgemeine Rahmenbedingungen
|
||||||
Die Finanzverwaltung wird als Modul / Erweiterung der bestehenden Software umgesetzt.
|
Die Finanzverwaltung wird als Modul / Erweiterung einer 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) pro angelegtem Verin.
|
Es gibt ein Vereinskonto und eine Währung (EUR).
|
||||||
|
|
||||||
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“ oder „Finanzverwaltung“).
|
Es wird eine zusätzliche Rolle für Finanzdaten integriert (z. B. „Kassenwart“, „Finanzverwaltung“).
|
||||||
|
|
||||||
Nur Benutzer mit dieser Rolle dürfen:
|
Nur Benutzer mit dieser Rolle dürfen:
|
||||||
|
|
||||||
|
|
@ -23,132 +23,275 @@ Finanzdaten ändern oder löschen
|
||||||
|
|
||||||
Berichte exportieren
|
Berichte exportieren
|
||||||
|
|
||||||
## 2.2 Zugriffsrechte
|
2.2 Zugriffsrechte
|
||||||
Benutzer ohne Finanzrolle haben Änderungsrechte auf Finanzdaten.
|
Benutzer ohne Finanzrolle haben keinen Zugriff auf Finanzdaten.
|
||||||
|
|
||||||
Leserechte und Schreibrechte müssen klar getrennt umsetzbar sein.
|
Leserechte und Schreibrechte müssen klar getrennt umsetzbar sein (falls von der bestehenden Software unterstützt).
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
## 3.2 Kategorietypen
|
frei definierbar
|
||||||
|
|
||||||
|
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
|
|
||||||
|
|
||||||
# 4. Buchungen (Kassenbuch)
|
Einnahmen-Kategorien
|
||||||
## 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)
|
|
||||||
|
|
||||||
## 4.2 Belege
|
Betrag (positiv, in EUR)
|
||||||
|
|
||||||
|
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:
|
|
||||||
- Freitextfeld oder Referenz zur Verknüpfung mit extern abgelegten Belegen
|
|
||||||
|
|
||||||
# 5. Kontostand und Kontenabgleich
|
Stattdessen:
|
||||||
## 5.1 Kontostand
|
|
||||||
|
Freitextfeld oder Referenz zur Verknüpfung mit extern abgelegten Belegen
|
||||||
|
|
||||||
|
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 pro Verein.
|
Es existiert genau ein Konto.
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|
||||||
# 6. Auswertungen und Berichte
|
sind normale Buchungen
|
||||||
## 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
|
|
||||||
|
|
||||||
## 6.2 Inhalt der Berichte
|
Monatsübersicht
|
||||||
- Anfangs- und Endsaldo
|
|
||||||
- Summe der Einnahmen
|
|
||||||
- Summe der Ausgaben
|
|
||||||
- Aufschlüsselung nach Kategorien
|
|
||||||
- Optionale Detailansicht aller Buchungen im Zeitraum
|
|
||||||
|
|
||||||
## 6.3 Export
|
Jahresü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
|
||||||
|
|
||||||
|
6.3 Export
|
||||||
Berichte müssen exportierbar sein als:
|
Berichte müssen exportierbar sein als:
|
||||||
- Excel (z. B. XLSX)
|
|
||||||
- PDF
|
|
||||||
|
|
||||||
# 7. Änderungsprotokoll (Audit-Log)
|
Excel (z. B. XLSX)
|
||||||
## 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
|
||||||
|
|
||||||
# 8. Bedienbarkeit und Usability
|
Änderungen an Buchungen
|
||||||
|
|
||||||
|
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 kleine Benutzeranleitung benötigt.
|
Es wird keine Benutzeranleitung benötigt.
|
||||||
|
|
||||||
Fokus liegt auf:
|
Fokus liegt auf:
|
||||||
- klaren Masken
|
|
||||||
- übersichtlichen Tabellen
|
klaren Masken
|
||||||
- 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
|
|
||||||
|
|
||||||
# 10. Erweiterbarkeit (optional, aber empfehlenswert)
|
Import von Bankumsätzen
|
||||||
|
|
||||||
|
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
|
|
||||||
|
|
||||||
# 11. Integration in bestehen Anwendung
|
Mehrere Konten
|
||||||
|
|
||||||
## 11.1
|
Erinnerungen
|
||||||
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
|
|
||||||
|
|
||||||
## 11.2
|
Schnittstellen
|
||||||
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,
|
||||||
|
|
@ -187,3 +330,51 @@ 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,9 +613,6 @@ 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,14 +25,7 @@
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private HttpContext HttpContext { get; set; } = default!;
|
private HttpContext HttpContext { get; set; } = default!;
|
||||||
|
|
||||||
private IComponentRenderMode? RenderModeForPage
|
private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
|
||||||
{
|
? new InteractiveServerRenderMode(prerender: false)
|
||||||
get
|
: new InteractiveServerRenderMode(prerender: true);
|
||||||
{
|
|
||||||
var path = HttpContext.Request.Path;
|
|
||||||
var disablePrerender = path.StartsWithSegments("/Account")
|
|
||||||
|| path.StartsWithSegments("/settings");
|
|
||||||
return new InteractiveServerRenderMode(prerender: !disablePrerender);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
@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,15 +144,6 @@
|
||||||
</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>
|
||||||
|
|
||||||
|
|
@ -164,7 +155,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/Schatzmeister/Admin)</MudText>
|
<MudText>Rollenbasierte Berechtigungen (Viewer/Editor/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,61 +196,6 @@ 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>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,11 +204,9 @@ 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()
|
||||||
{
|
{
|
||||||
|
|
@ -274,34 +217,19 @@ 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);
|
||||||
_isLoading = false;
|
await LoadInvitationsAsync();
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,23 +267,6 @@ 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());
|
||||||
|
|
@ -550,47 +461,4 @@ 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,4 +1,3 @@
|
||||||
using System.Globalization;
|
|
||||||
using ApexCharts;
|
using ApexCharts;
|
||||||
using Fluxor;
|
using Fluxor;
|
||||||
using Fluxor.Blazor.Web.ReduxDevTools;
|
using Fluxor.Blazor.Web.ReduxDevTools;
|
||||||
|
|
@ -141,8 +140,4 @@ 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