whisper-money/app
Víctor Falcón a9b90a200e
feat(connections): manage which accounts a bank connection syncs (#558)
## Why

During the bank connection flow (Enable Banking) a user picks which
accounts to sync and can skip the rest. Until now there was **no way
back**: a skipped account couldn't be enabled later, a synced account
couldn't be moved or turned off, and accounts the bank added afterwards
were invisible. Skipped accounts aren't persisted anywhere —
`pending_accounts_data` is cleared once mapping completes — so the app
simply forgets they exist.

This adds a per-connection **Manage Accounts** screen that closes that
gap.

## What

- **Known accounts render from the DB** (no provider call). For each
synced account the user can:
- **Stop syncing** → the account is unlinked and becomes a regular
manual account. Non-destructive: it keeps all its imported transactions.
- **Change destination** → move syncing to another compatible manual
account (unlinks the previous holder, links the new one with incremental
sync).
- **Load accounts** button re-fetches the consent's account list from
the provider *on demand* (`getSession` + `getAccount` only for uids we
don't already know) to surface newly available / previously skipped
accounts, which can then be created as new accounts or linked to
existing manual ones.
- The provider is **only ever hit on refresh**; every mutation is
DB-only, so the screen still works (read + rearrange) when the consent
has expired.

### Design notes

- No new column / cache: the default view derives entirely from the
`accounts` table, and refresh is on-demand and ephemeral. Re-fetching is
mandatory anyway (existing connections have no stored snapshot, and only
a live call can reveal accounts the bank added later).
- Scoped to **Enable Banking** for now; the approach is column-free so
extending to the crypto/API-key providers later is trivial.

## Endpoints

- `GET open-banking/connections/{connection}/accounts` — manage screen
(`discoveredAccounts` computed only when `?refresh=1`)
- `POST open-banking/connections/{connection}/accounts/map` — create /
link / change destination for one bank account
- `POST open-banking/connections/{connection}/accounts/{account}/unlink`
— stop syncing

## Testing

- New `ConnectionAccountTest` (7 tests): index props, refresh discovery
(excludes already-synced uids), create,
change-destination/unlink-previous, non-destructive unlink keeps
transactions, ownership 403, account-mismatch 404.
- Full `tests/Feature/OpenBanking` suite green (268), plus pint, eslint,
prettier and the enforced `es` localization test.

## Deliberately skipped

- **Subscription gating** on refresh/map — matches the currently ungated
`disconnect`. Easy follow-up if free users shouldn't trigger live
provider calls.
- Other providers (Enable Banking only, as agreed).
2026-06-18 16:22:49 +02:00
..
Actions refactor: centralize duplicated provider & locale keys into enums (#543) 2026-06-16 13:43:14 +00:00
Ai/Agents feat(ai): auto-categorize transactions with AI (behind flag) (#535) 2026-06-15 16:35:20 +02:00
Console/Commands feat(integration-requests): markdown comments and in-progress status (#553) 2026-06-18 08:36:43 +00:00
Contracts feat(connections): manage which accounts a bank connection syncs (#558) 2026-06-18 16:22:49 +02:00
Enums feat(connections): manage which accounts a bank connection syncs (#558) 2026-06-18 16:22:49 +02:00
Events Add Budgeting Feature to Track and Manage Spending (#36) 2026-01-21 15:25:50 +01:00
Exceptions/Banking fix(banking): handle EnableBanking expired sessions as reconnect, not error (#557) 2026-06-18 13:48:13 +00:00
Features feat(connections): manage which accounts a bank connection syncs (#558) 2026-06-18 16:22:49 +02:00
Http feat(connections): manage which accounts a bank connection syncs (#558) 2026-06-18 16:22:49 +02:00
Jobs fix(banking): handle EnableBanking expired sessions as reconnect, not error (#557) 2026-06-18 13:48:13 +00:00
Listeners fix(discord): skip zero-amount payment stats messages (#540) 2026-06-15 19:21:47 +02:00
Mail feat(leads): add user lead re-invite campaign (#432) 2026-05-26 08:35:31 +02:00
Models feat(integration-requests): add not-doable status with a public comment (#552) 2026-06-17 16:36:32 +02:00
Notifications fix: verify email via signed link without requiring login (#490) 2026-06-05 10:01:32 +02:00
Policies refactor(policies): extract HandlesUserOwnership trait (#478) 2026-06-03 17:43:30 +02:00
Providers feat(ai): auto-categorize transactions with AI (behind flag) (#535) 2026-06-15 16:35:20 +02:00
Rules feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
Services fix(banking): handle EnableBanking expired sessions as reconnect, not error (#557) 2026-06-18 13:48:13 +00:00