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.
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
Transactiontypepayment+ allTeamWalletTransaction(credit,expense) count.fine/levy/feeare excluded from every chart in this feature. - Amounts are stored positive in the DB; sign convention when summing must match
setBalance():expense(type.id14) subtracts,payment/creditadds. balanceHistory's last entry must equalteam.balance(sanity check, cover in a test).- Chart library is
chart.jsonly — do not addng2-charts/ngx-charts(Angular 21 peer-dependency risk). topOutstandingreturns at most 10 active players (active === true,balance < 0), sorted by debt descending, with a link to the existingmembersroute for the full list — do not render all players in the chart.- No new endpoints/UI outside the
Übersichtpage (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:balanceHistorymonthly bucketing over 12 months with carry-forward for months without movement and correct sign handling (expense subtracted, payment/credit added, matchingsetBalance());monthlyFlowgrouping wherepayment+creditsum intoincomeandexpensesums intoexpense, withfine/levy/feeexcluded entirely;topOutstandinglimited to 10 active players withbalance < 0, sorted by debt descending; and the sanity checkbalanceHistory.at(-1).balance === team.balance. -
Write a failing controller test for
GET :id/overview/statsasserting it carries the same@UseGuards(AuthGuard('jwt'), RolesGuard)/@Roles([RoleEnum.user, RoleEnum.admin])as the existing:id/overviewroute and delegates toservice.getOverviewStats(id). -
Run the focused backend tests and confirm they fail for the expected reason (method/route missing).
-
Implement
getOverviewStatsinteams.service.ts: load the team withrelations: ['players', 'players.transactions', 'transactions'](same pattern asgetTeamTransactions), build one date-sorted list fromteam.transactions(TeamWalletTransaction) plus each player'stransactionsfiltered totype.name === 'payment', derivebalanceHistory(12 monthly points, cumulative sum with the sign rule above, carry-forward on empty months),monthlyFlow(same 12 months,payment/credit→income,expense→expense), andtopOutstanding(active players,balance < 0, sorted, sliced to 10, mapped to{ playerId, playerName: firstName + ' ' + lastName, balance: outstanding as positive number }). Add theGET ':id/overview/stats'route toteams.controller.tsnext to:id/overview, delegating to the new service method. -
Run the focused backend tests plus
npm run buildinmyteamwallet_backend; commit the backend slice.
Task 2: Frontend chart infrastructure and overview integration
Files:
-
Modify:
myteamwallet_frontend_modern/package.json(addchart.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.jsinmyteamwallet_frontend_modern(no Angular wrapper package). -
Write failing tests for
TeamStatsApi.loadStats(teamId)(GETteams/:id/overview/stats, mirrorsTransactionsApi.loadTeamTransactions) and theTeamOverviewStatsmodel shape. -
Write failing tests for the
ChartCanvasshared component: it creates aChart.jsinstance fromtype/data/optionsinputs, updates the instance when those inputs change, and destroys it onngOnDestroy. -
Write failing tests in
overview.spec.tsfor the three new chart cards: spinner whileloadingStats()is true, empty-state per card when its dataset is an empty array, data reachingChartCanvasonceTeamStatsApi.loadStatsresolves, silent empty-state (no thrown error) when the request errors, and a workingrouterLinkfrom the Top-10 card to the team'smembersroute. -
Run the focused frontend tests and confirm they fail for the expected reason.
-
Implement
TeamOverviewStatsmodel andTeamStatsApiservice. -
Implement
ChartCanvas: a<canvas>-backed component withtype/data/optionsinputs that manages theChartinstance lifecycle viaeffect()andngOnDestroy. -
Implement the
Overviewchanges: astats/loadingStatssignal pair fed byteamStatsApi.loadStats(id)through the same route-paramswitchMap+catchError(() => of(null))pattern already used foractivities; three newmat-cardsections 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 tomembers), 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.