|
|
|
|
@@ -0,0 +1,111 @@
|
|
|
|
|
# Theoretischer Kassenstand (Ist + offene Beiträge) im Kassenstand-Verlauf
|
|
|
|
|
|
|
|
|
|
Status: approved
|
|
|
|
|
Datum: 2026-08-03
|
|
|
|
|
|
|
|
|
|
## Kontext
|
|
|
|
|
|
|
|
|
|
Ergänzung zum bestehenden Kassenstand-Verlauf-Chart aus
|
|
|
|
|
`docs/superpowers/specs/2026-08-01-kasse-kpi-charts-design.md`. Der Chart auf der Team-Übersicht
|
|
|
|
|
(`features/team/overview/overview.ts` + `.html`) zeigt aktuell eine Linie „Kassenstand" über die
|
|
|
|
|
letzten 12 Monate, gespeist aus `GET /teams/:id/overview/stats` bzw.
|
|
|
|
|
`teams.service.ts#getOverviewStats`.
|
|
|
|
|
|
|
|
|
|
Auf derselben Übersicht existiert bereits eine zweite Kennzahl „Offene Beiträge" (aus
|
|
|
|
|
`teams.service.ts#getOverview`, Zeile 46-53): `-(Summe der `balance` aller aktiven Spieler)`. Sie
|
|
|
|
|
zeigt nur den heutigen Wert, keinen Verlauf.
|
|
|
|
|
|
|
|
|
|
Ziel: eine zweite Linie im bestehenden Chart, die pro Monat den theoretischen Kassenstand zeigt —
|
|
|
|
|
also „was wäre in der Kasse, wenn alle offenen Beiträge bereits bezahlt worden wären" —, um Trainer/
|
|
|
|
|
Kassenwarte auf einen Blick erkennen zu lassen, wie stark der Ist-Stand vom Soll-Stand abweicht.
|
|
|
|
|
|
|
|
|
|
## Entscheidungen aus dem Brainstorming
|
|
|
|
|
|
|
|
|
|
- **Historie statt Snapshot**: Die zweite Linie zeigt für jeden Monat die zu diesem Zeitpunkt
|
|
|
|
|
tatsächlich offenen Beiträge, nicht den heutigen Wert konstant über alle 12 Punkte addiert.
|
|
|
|
|
- **Näherung wie beim bestehenden Kassenstand-Verlauf**: Es wird mit der *heutigen* Menge aktiver
|
|
|
|
|
Spieler gerechnet, kein historisches Tracking von Mitgliedschaft/Aktiv-Status. Das ist dieselbe
|
|
|
|
|
Vereinfachung, die `balanceHistory` bereits für den Kassenstand selbst nutzt (siehe
|
|
|
|
|
`getOverviewStats`-Kommentar zu `team.balance` als Anker).
|
|
|
|
|
- **Datenform**: `theoreticalBalance` wird als zusätzliches Feld direkt in jeden bestehenden
|
|
|
|
|
`balanceHistory`-Punkt eingebettet (`{ month, balance, theoreticalBalance }`), kein separates
|
|
|
|
|
Array — additive, nicht-brechende Erweiterung der bestehenden Response.
|
|
|
|
|
|
|
|
|
|
## Architektur / Komponenten
|
|
|
|
|
|
|
|
|
|
### 1. Backend: `teams.service.ts#getOverviewStats`
|
|
|
|
|
|
|
|
|
|
Neue private Hilfsberechnung, analog zur bestehenden Rückwärts-Rekonstruktion von `balanceHistory`
|
|
|
|
|
(Zeile ~290-320), aber auf Spieler-Ebene statt Team-Ebene:
|
|
|
|
|
|
|
|
|
|
- Datenquelle: `team.players` (bereits geladen über `relations: ['players', 'players.transactions',
|
|
|
|
|
'transactions']`), gefiltert auf `player.active` — dieselbe Teilmenge, die `getOverview` für die
|
|
|
|
|
heutige „Offene Beiträge"-Kachel verwendet.
|
|
|
|
|
- Für jeden aktiven Spieler: dessen `transactions` (bereits geladen), **ausgenommen** Zeilen mit
|
|
|
|
|
`note?.startsWith(DEACTIVATION_ADJUSTMENT_NOTE_PREFIX)` (Import aus
|
|
|
|
|
`team-members.service.ts`) — dieselbe Ausschlussregel wie in
|
|
|
|
|
`TeamMembersService.recomputeBalance`, damit synthetische Ausgleichsbuchungen die Historie nicht
|
|
|
|
|
verfälschen.
|
|
|
|
|
- Vorzeichen je Buchung: `type.id > 10` (Strafe/Umlage/Gebühr, IDs 11-13) mindert den Spieler-Saldo,
|
|
|
|
|
alle anderen Typen (`payment`, `credit`) erhöhen ihn — identische Regel wie in
|
|
|
|
|
`TeamMembersService.recomputeBalance` (Zeile ~127-133) und `TransactionsService.reverse()`
|
|
|
|
|
(`type.id > 10`-Check).
|
|
|
|
|
- Rekonstruktion: ausgehend von `player.balance` (aktueller, autoritativer Wert) rückwärts durch die
|
|
|
|
|
nach Datum absteigend sortierten Buchungen laufen und pro Monat der letzten 12 Monate den
|
|
|
|
|
rekonstruierten Saldo am Monatsende ermitteln — strukturell identisch zum bestehenden
|
|
|
|
|
`descendingMovements`/`futureSum`-Muster für `balanceHistory`, nur pro Spieler statt einmal fürs
|
|
|
|
|
Team.
|
|
|
|
|
- Pro Monat: `outstandingAtMonth = -Σ(rekonstruierter Saldo aktiver Spieler)`,
|
|
|
|
|
`theoreticalBalance = balanceHistory[monat].balance + outstandingAtMonth`.
|
|
|
|
|
- Rückgabeform ändert sich zu:
|
|
|
|
|
```ts
|
|
|
|
|
balanceHistory: { month: string; balance: number; theoreticalBalance: number }[]
|
|
|
|
|
```
|
|
|
|
|
`monthlyFlow` und `topOutstanding` bleiben unverändert.
|
|
|
|
|
- Gating unverändert: Ist `movements.length === 0` (keine Kassenbewegung je), bleibt
|
|
|
|
|
`balanceHistory: []` wie heute — ein Team mit ausschließlich unbezahlten Strafen, aber ganz ohne
|
|
|
|
|
Zahlungsbewegung, zeigt weiterhin keinen Chart (Out of Scope, siehe unten).
|
|
|
|
|
|
|
|
|
|
### 2. Frontend: `overview.ts` / Chart-Konfiguration
|
|
|
|
|
|
|
|
|
|
- `models/team-stats.model.ts`: `BalanceHistoryPoint` um `theoreticalBalance: number` erweitern.
|
|
|
|
|
- `balanceChartData` (computed) bekommt eine zweite Dataset-Eintrag:
|
|
|
|
|
- Label: „Theoretisch (inkl. offene Beiträge)"
|
|
|
|
|
- `data: points.map((p) => p.theoreticalBalance)`
|
|
|
|
|
- Gestrichelt (`borderDash: [6, 4]`), eigene Farbe `#1d70b8` (Blauton, klar unterscheidbar vom
|
|
|
|
|
Grün `#4f8f46` der Ist-Linie), `fill: false`.
|
|
|
|
|
- `balanceChartOptions`: `plugins.legend.display` von `false` auf `true` (bzw. `position: 'bottom'`
|
|
|
|
|
wie beim Flow-Chart), da jetzt zwei Linien unterschieden werden müssen.
|
|
|
|
|
- Keine Änderung an `ChartCanvas` (shared component) nötig — reine Config-/Daten-Änderung.
|
|
|
|
|
|
|
|
|
|
## Fehlerbehandlung
|
|
|
|
|
|
|
|
|
|
Unverändert zum bestehenden Muster: Fehler beim Laden der Stats führen zum bestehenden stillen
|
|
|
|
|
Empty-State der Chart-Karte. Kein neuer Fehlerfall durch diese Erweiterung.
|
|
|
|
|
|
|
|
|
|
## Testing
|
|
|
|
|
|
|
|
|
|
- Backend (`teams.service.spec.ts`, Erweiterung des bestehenden `getOverviewStats`-Testblocks):
|
|
|
|
|
- Sanity-Check: `balanceHistory.at(-1).theoreticalBalance === team.balance + aktuelle Summe
|
|
|
|
|
offener Beiträge` (heutiger Wert, wie von `getOverview` berechnet).
|
|
|
|
|
- Historische Rekonstruktion: Testfall mit einer Strafe (`fine`) in einem früheren Monat, die erst
|
|
|
|
|
im aktuellen Monat bezahlt wurde — `theoreticalBalance` im früheren Monat muss die damals
|
|
|
|
|
offene Strafe enthalten, `balance` (Ist) nicht.
|
|
|
|
|
- Deaktivierungs-Ausgleichsbuchungen werden aus der Rekonstruktion ausgeschlossen (Testfall mit
|
|
|
|
|
einem zwischenzeitlich deaktivierten und wieder aktivierten Spieler).
|
|
|
|
|
- Leerfall (`movements.length === 0`) liefert weiterhin `balanceHistory: []`.
|
|
|
|
|
- Frontend (`overview.spec.ts`): Erweiterung des bestehenden Chart-Daten-Tests um Assertion, dass
|
|
|
|
|
`balanceChartData()` zwei Datasets enthält und die zweite Serie aus `theoreticalBalance` gespeist
|
|
|
|
|
wird.
|
|
|
|
|
- Manuelle Verifikation: Team mit einer unbezahlten Strafe/Umlage lokal aufrufen, prüfen dass die
|
|
|
|
|
theoretische Linie sichtbar über der Ist-Linie liegt und bei vollständiger Bezahlung beide Linien
|
|
|
|
|
zusammenlaufen.
|
|
|
|
|
|
|
|
|
|
## Out of Scope
|
|
|
|
|
|
|
|
|
|
- Historisches Tracking von Mitgliedschaft/Aktiv-Status (Näherung mit heutiger aktiver
|
|
|
|
|
Spieler-Menge, siehe oben).
|
|
|
|
|
- Teams mit ausschließlich unbezahlten Strafen/Umlagen/Gebühren, aber ganz ohne Kassenbewegung —
|
|
|
|
|
zeigen weiterhin keinen Chart (bestehende Einschränkung aus dem Basis-Feature, nicht neu
|
|
|
|
|
eingeführt).
|
|
|
|
|
- Zeitraum-Umschalter (weiterhin feste letzte 12 Monate, wie im Basis-Feature festgelegt).
|