Task-by-task TDD plan covering manual CSV/PDF export, the recurring
PDF-mailing subscription, and the frontend wiring, grounded in the
existing recurring-transactions module and mail service as precedent.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers on-demand CSV/PDF export of real cash-affecting transactions
and an optional per-team recurring PDF mailing to arbitrary email
addresses, following the same brainstorming process used for
recurring transactions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets treasurers/captains/coaches define recurring fee/levy dues that
are automatically booked for all active players on a monthly,
quarterly, or yearly schedule via a daily cron job, instead of having
to book them manually every cycle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Task-by-task plan covering the loosened create-team role guard,
auto-captain membership on creation, the new TeamsApi/MyTeamsStore
methods, the CreateTeamDialog component, and wiring it into
team-select.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Brainstormed with the user: any logged-in user should be able to
self-service create a team and becomes its captain, via a dialog on
team-select. Team deletion/archiving is scoped out as a separate
follow-up feature.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- teams.service.spec.ts: pick a checkpoint older than the adjustment's own
month so the exclusion test actually fails without the exclusion filter
- teams.service.ts: only negate fine/levy/fee amounts when positive,
matching TeamMembersService.recomputeBalance and Transaction.setBalance()
exactly, instead of negating unconditionally
- teams.service.ts: outstanding-history helper now returns a positive
value when players owe money, matching the house convention already
established by getOverview()'s team.outstanding
- overview.spec.ts: assert the balance chart's legend becomes visible
- teams.service.spec.ts: add coverage for inactive-player exclusion and
a positive-balance (prepaid credit) case
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a second, dashed line to the existing balance-history chart that
includes currently open player dues, so managers can see at a glance
how far the actual cash balance lags behind what has been pledged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extract the month-by-month backward-walk algorithm (used for both
team-level cash balance and player-level debt history) into a single
shared private helper `reconstructBackward()`. This eliminates code
duplication while preserving behavior:
- Team-level balanceHistory: maps movements through signedFlowAmount(),
rounds each point, calls the shared helper
- Player-level balance history: maps transactions using type.id rule,
skips rounding (only rounds at final merge to avoid compounding
errors), calls the shared helper
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reconstructs each active player's balance per month (same backward
technique as the existing cash-balance history) so the overview stats
endpoint can report what the team balance would be if all currently
open dues had already been paid.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a second historical line to the existing cash-balance chart showing
what the balance would be if all outstanding player dues were paid,
reconstructed per month the same way the existing balance line is.
Whole-branch review findings:
1. balanceHistory/monthlyFlow always returned 12 entries, even for a
brand-new team with zero transactions, so the frontend's empty-state
(gated on .length === 0) could never fire for a real "no movements yet"
team. Now returns empty arrays when there are no relevant movements at
all (not just none in the last 12 months, so a team with older-but-real
history still gets a flat chart). Also added the same defensive
`?? []` guard on players/transactions that getOverview already has, so a
team with no players/relations loaded doesn't throw.
2. GET :id/overview/stats had no team-membership check -- any logged-in
user (RoleEnum.user is the default role) could read any other team's
financial stats by iterating ids. Injected TeamAccessService into
TeamsService (already a sibling provider in TeamsModule, no module
wiring needed) and call assertMember(actorUserId, teamId) as the first
line of getOverviewStats, threaded from the controller via @Req(). Read
access only (assertMember, not assertManager), matching who can already
view the overview page. Sibling routes with the same pre-existing gap
were left untouched, per review scope.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Manual verification against real data (Task 3) found balanceHistory drifted
from team.balance for real teams, since team.balance carries historical
adjustments (e.g. from the removed legacy backend) that don't trace back to
the current payment/credit/expense rows. Forward-summing those rows from
zero could never be trusted to tie out.
Rewrite balanceHistory to anchor on team.balance (the authoritative current
value) and walk the movements backward, newest to oldest, undoing each one
to reconstruct earlier month-end balances. This guarantees the most recent
point equals team.balance by construction, and is mathematically identical
to the old forward sum for teams whose movements fully explain their
balance. monthlyFlow/topOutstanding are unaffected and left as-is.
Updated teams.service.spec.ts to use a fixture where team.balance
intentionally does not equal the sum of its own movements, so the tests
actually exercise the drift-handling behavior instead of a case where
forward-sum and backward-anchor happen to coincide.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The vi.mock('chart.js', ...) MockChart class was copy-pasted verbatim
between chart-canvas.spec.ts and overview.spec.ts. Extract it to
shared/chart-canvas/testing/mock-chart.ts and import it via
vi.hoisted(async () => import(...)) in each spec, since vi.mock's
factory is hoisted above regular imports and can't reference a
plain top-level import.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds three chart.js-backed KPI cards (Kassenstand-Verlauf, Einnahmen &
Ausgaben, Top-10 offene Beitraege) to the existing Uebersicht page,
consuming the new GET teams/:id/overview/stats endpoint via a new
TeamStatsApi service. Introduces a small reusable ChartCanvas shared
component that wraps the Chart.js instance lifecycle via @Input()/
ngOnChanges, following this codebase's existing input-decorator
convention rather than effect().
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>