From c1238929efaf26554ee374fabb108a0b98ec6b99 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Sat, 1 Aug 2026 19:13:56 +0200 Subject: [PATCH] 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. --- .../plans/2026-08-01-kasse-kpi-charts.md | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-01-kasse-kpi-charts.md diff --git a/docs/superpowers/plans/2026-08-01-kasse-kpi-charts.md b/docs/superpowers/plans/2026-08-01-kasse-kpi-charts.md new file mode 100644 index 0000000..9371a3f --- /dev/null +++ b/docs/superpowers/plans/2026-08-01-kasse-kpi-charts.md @@ -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 ``-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.