docs: add implementation plan for cashbox KPI charts

Approved plan for the three overview charts (balance history, monthly
income/expense, top-10 outstanding players): new backend aggregation
endpoint plus a chart.js-based frontend integration on the existing
overview page.
This commit is contained in:
Bastian Wagner
2026-08-01 19:13:56 +02:00
parent b6f311b11b
commit c1238929ef

View File

@@ -0,0 +1,67 @@
# Kasse-KPI-Charts Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add three KPI charts (Kassenstand-Verlauf, Einnahmen/Ausgaben pro Monat, Top-10 offene Beiträge) to the existing team `Übersicht` page.
**Architecture:** A new read-only backend aggregation endpoint (`GET /teams/:id/overview/stats`) derives all three datasets at request time from existing `Transaction`/`TeamWalletTransaction` data — no new tables. The frontend renders them with a small reusable `ChartCanvas` component wrapping `chart.js` directly (no Angular chart wrapper), following the existing `switchMap`/`catchError` loading pattern already used for `activities` in `overview.ts`.
**Tech Stack:** NestJS 9, TypeORM 0.3, Jest (backend); Angular 21, Angular Material, signals/RxJS, chart.js (new dependency), Vitest (frontend).
Design spec: `docs/superpowers/specs/2026-08-01-kasse-kpi-charts-design.md`.
## Global Constraints
- Default/only time window: last 12 months, no picker.
- "Ist-Kasse" rule: only `Transaction` type `payment` + all `TeamWalletTransaction` (`credit`, `expense`) count. `fine`/`levy`/`fee` are excluded from every chart in this feature.
- Amounts are stored positive in the DB; sign convention when summing must match `setBalance()`: `expense` (`type.id` 14) subtracts, `payment`/`credit` adds.
- `balanceHistory`'s last entry must equal `team.balance` (sanity check, cover in a test).
- Chart library is `chart.js` only — do not add `ng2-charts`/`ngx-charts` (Angular 21 peer-dependency risk).
- `topOutstanding` returns at most 10 active players (`active === true`, `balance < 0`), sorted by debt descending, with a link to the existing `members` route for the full list — do not render all players in the chart.
- No new endpoints/UI outside the `Übersicht` page (no new route/tab).
- No dark-mode-specific chart theming (app is light-only today).
---
### Task 1: Backend stats endpoint
**Files:**
- Modify: `myteamwallet_backend/src/teams/teams.controller.ts`
- Modify: `myteamwallet_backend/src/teams/teams.service.ts`
- Test: `myteamwallet_backend/src/teams/teams.service.spec.ts`
- Test: `myteamwallet_backend/src/teams/teams.controller.spec.ts`
- [ ] Write failing tests for a new `getOverviewStats(teamId)` service method covering: `balanceHistory` monthly bucketing over 12 months with carry-forward for months without movement and correct sign handling (expense subtracted, payment/credit added, matching `setBalance()`); `monthlyFlow` grouping where `payment`+`credit` sum into `income` and `expense` sums into `expense`, with `fine`/`levy`/`fee` excluded entirely; `topOutstanding` limited to 10 active players with `balance < 0`, sorted by debt descending; and the sanity check `balanceHistory.at(-1).balance === team.balance`.
- [ ] Write a failing controller test for `GET :id/overview/stats` asserting it carries the same `@UseGuards(AuthGuard('jwt'), RolesGuard)` / `@Roles([RoleEnum.user, RoleEnum.admin])` as the existing `:id/overview` route and delegates to `service.getOverviewStats(id)`.
- [ ] Run the focused backend tests and confirm they fail for the expected reason (method/route missing).
- [ ] Implement `getOverviewStats` in `teams.service.ts`: load the team with `relations: ['players', 'players.transactions', 'transactions']` (same pattern as `getTeamTransactions`), build one date-sorted list from `team.transactions` (TeamWalletTransaction) plus each player's `transactions` filtered to `type.name === 'payment'`, derive `balanceHistory` (12 monthly points, cumulative sum with the sign rule above, carry-forward on empty months), `monthlyFlow` (same 12 months, `payment`/`credit``income`, `expense``expense`), and `topOutstanding` (active players, `balance < 0`, sorted, sliced to 10, mapped to `{ playerId, playerName: firstName + ' ' + lastName, balance: outstanding as positive number }`). Add the `GET ':id/overview/stats'` route to `teams.controller.ts` next to `:id/overview`, delegating to the new service method.
- [ ] Run the focused backend tests plus `npm run build` in `myteamwallet_backend`; commit the backend slice.
### Task 2: Frontend chart infrastructure and overview integration
**Files:**
- Modify: `myteamwallet_frontend_modern/package.json` (add `chart.js`)
- Create: `myteamwallet_frontend_modern/src/app/shared/chart-canvas/chart-canvas.ts` (+ `.html`/`.scss`/`.spec.ts`)
- Create: `myteamwallet_frontend_modern/src/app/core/team/team-stats-api.ts` (+ `.spec.ts`)
- Create: `myteamwallet_frontend_modern/src/app/models/team-stats.model.ts`
- Modify: `myteamwallet_frontend_modern/src/app/features/team/overview/overview.ts`
- Modify: `myteamwallet_frontend_modern/src/app/features/team/overview/overview.html`
- Modify: `myteamwallet_frontend_modern/src/app/features/team/overview/overview.scss`
- Modify: `myteamwallet_frontend_modern/src/app/features/team/overview/overview.spec.ts`
- [ ] Install `chart.js` in `myteamwallet_frontend_modern` (no Angular wrapper package).
- [ ] Write failing tests for `TeamStatsApi.loadStats(teamId)` (GET `teams/:id/overview/stats`, mirrors `TransactionsApi.loadTeamTransactions`) and the `TeamOverviewStats` model shape.
- [ ] Write failing tests for the `ChartCanvas` shared component: it creates a `Chart.js` instance from `type`/`data`/`options` inputs, updates the instance when those inputs change, and destroys it on `ngOnDestroy`.
- [ ] Write failing tests in `overview.spec.ts` for the three new chart cards: spinner while `loadingStats()` is true, empty-state per card when its dataset is an empty array, data reaching `ChartCanvas` once `TeamStatsApi.loadStats` resolves, silent empty-state (no thrown error) when the request errors, and a working `routerLink` from the Top-10 card to the team's `members` route.
- [ ] Run the focused frontend tests and confirm they fail for the expected reason.
- [ ] Implement `TeamOverviewStats` model and `TeamStatsApi` service.
- [ ] Implement `ChartCanvas`: a `<canvas>`-backed component with `type`/`data`/`options` inputs that manages the `Chart` instance lifecycle via `effect()` and `ngOnDestroy`.
- [ ] Implement the `Overview` changes: a `stats`/`loadingStats` signal pair fed by `teamStatsApi.loadStats(id)` through the same route-param `switchMap` + `catchError(() => of(null))` pattern already used for `activities`; three new `mat-card` sections between the balance-grid and the activity list (Kassenstand-Verlauf line chart, Einnahmen/Ausgaben grouped bar chart, Top-10-Schuldner horizontal bar chart with a "Alle Spieler ansehen" link to `members`), each with its own loading/empty state.
- [ ] Run the focused frontend tests, the full frontend test suite, and the TypeScript checks; commit the frontend slice.
### Task 3: Integration and review
- [ ] Run the full backend test suite and build, the full frontend test suite and typecheck, and `git diff --check`.
- [ ] Manually verify against a running instance: a team with transaction history renders all three charts with correct values; a team with no financial movements shows empty-states, not errors or blank canvases.
- [ ] Request a read-only code review of the full diff range; fix Critical/Important findings and re-verify.
- [ ] Run the branch-finishing workflow and preserve the worktree until the user chooses integration.