Files
teamwallet/docs/superpowers/plans/2026-08-01-kasse-kpi-charts.md
Bastian Wagner c1238929ef 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.
2026-08-01 19:13:56 +02:00

7.3 KiB

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/creditincome, expenseexpense), 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.