whisper-money/app
Víctor Falcón b661255d09
refactor: extract AccountMetricsService to deduplicate balance computation logic (#147)
## Why

### Problem
Account balance metrics computation (12-month sparklines, net worth
evolution, currency conversion) was duplicated across three controllers:
- `AccountController` — account index page metrics
- `DashboardController` — dashboard net worth evolution
- `DashboardAnalyticsController` — analytics API net worth evolution
(monthly + daily)

Each had its own `convertBalance()` method, its own BalanceLookup
iteration loop, and its own invested amount handling. Additionally,
`AccountController::show()` re-queried `categories`, `accounts`, and
`banks` — all already available as shared Inertia props from
`HandleInertiaRequests` middleware.

## What

### Changes
- **Extract `AccountMetricsService`** with three public methods:
- `getAccountMetrics()` — per-account balance metrics with sparkline
history (used by accounts index)
- `getNetWorthEvolution()` — monthly net worth evolution with
per-account balances (used by dashboard + analytics API)
- `getNetWorthDailyEvolution()` — daily net worth evolution (used by
analytics API)
- **Refactor `AccountController`** — delegate to
`AccountMetricsService`, remove 3 private methods (`getAccountMetrics`,
`formatMonth`, `convertBalance`)
- **Refactor `DashboardController`** — delegate to
`AccountMetricsService`, remove `getNetWorthEvolution` loop and
`convertBalance`
- **Refactor `DashboardAnalyticsController`** — delegate to
`AccountMetricsService` for `netWorthEvolution` and
`netWorthDailyEvolution`, remove `convertBalance`, `getBalanceAt`,
`getInvestedAmountAt` private methods
- **Remove duplicate queries from `AccountController::show()`** —
`categories`, `accounts`, `banks` are already shared by middleware
- **Add missing `encrypted` column** to the middleware's shared
`accounts` query so components that need it (EditAccountDialog, etc.)
receive it

### Impact
- **Net -87 lines** across 5 files (285 added in new service, 372
removed from controllers)
- Single source of truth for balance metrics computation
- Eliminated 3 copies of `convertBalance()` and 2 copies of the net
worth evolution loop
- Removed 3 redundant database queries from account show page

## Verification

### Tests
- All 84 related tests pass (AccountController, AccountBalance,
Dashboard, DashboardAnalytics, BalanceLookup, CashflowAnalytics) with
529 assertions
- No test changes needed — the refactoring preserves exact output shapes
2026-02-24 09:07:42 +01:00
..
Actions feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
Console/Commands perf: make banking syncs incremental on subsequent runs (#141) 2026-02-23 15:10:40 +01:00
Contracts feat: Integrate EnableBanking as open banking provider (#106) 2026-02-12 09:09:28 +01:00
Enums feat: enable invested amount tracking for savings accounts (#142) 2026-02-23 17:19:33 +01:00
Events Add Budgeting Feature to Track and Manage Spending (#36) 2026-01-21 15:25:50 +01:00
Http refactor: extract AccountMetricsService to deduplicate balance computation logic (#147) 2026-02-24 09:07:42 +01:00
Jobs perf: make banking syncs incremental on subsequent runs (#141) 2026-02-23 15:10:40 +01:00
Listeners feat: Apply automation rules to bank-synced transactions (#112) 2026-02-12 14:20:55 +01:00
Mail Email notification for bank-synced transactions (#113) 2026-02-12 14:05:02 +01:00
Models feat: investment benefits — show gains/losses on investment accounts (#140) 2026-02-23 13:59:10 +01:00
Notifications feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
Policies Add Budgeting Feature to Track and Manage Spending (#36) 2026-01-21 15:25:50 +01:00
Providers Remove plaintext-transactions feature flag & E2E references (#116) 2026-02-13 11:10:21 +01:00
Services refactor: extract AccountMetricsService to deduplicate balance computation logic (#147) 2026-02-24 09:07:42 +01:00