Investment portfolio charts, search fix, and holding creation fixes

- Add four portfolio charts: allocation donut by holding, allocation
  donut by asset type, cost basis vs current value bar, return % bar
- Fix asset search to use yf.Search() full-text instead of ticker-only
  lookup — name searches like "vanguard ftse all world" now work
- Fix holding creation double-quantity bug: holdings now created with
  quantity=0 so buy transaction is sole source of quantity/cost basis
- Add per-share / total price toggle in Add Holding modal with live
  calculated equivalent shown as you type
- Add ErrorBoundary in AppShell so render errors show a message instead
  of a blank page
- Fix donut charts using || instead of ?? when falling back from
  current_value to cost_basis_total (0 was not falling through ??)
- Allow HoldingCreate.quantity >= 0 (was gt=0) to support zero-init
- Fix error display for Pydantic v2 array-of-objects validation errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-04-22 23:06:41 +00:00
parent 26e2a055db
commit cdc1e67321
9 changed files with 424 additions and 70 deletions

View file

@ -90,6 +90,11 @@ backend/app/
- Soft deletes: `deleted_at` timestamp; all queries must filter `WHERE deleted_at IS NULL`
- `_to_response()` in `transaction_service.py` must include all fields returned to the frontend — omitting a field here makes it invisible to the UI even if it's in the DB
### Investment data model
- Asset search uses `yf.Search(query)` (full-text, name or ticker) in `price_feed_service.search_yahoo()` — not ticker-only lookup
- `HoldingCreate.quantity` allows `ge=0` (zero) — holdings are created with quantity=0, and `add_investment_transaction()` drives the quantity via cumulative buy/sell updates. Never create a holding with the target quantity and also add a matching buy transaction, or the quantity doubles
- `PortfolioCharts.tsx` uses `||` (not `??`) when falling back from `current_value` to `cost_basis_total``??` fails when `current_value` is `0` rather than null
### AI / receipt parsing (`api/v1/settings.py`, `api/v1/transactions.py`)
- User AI config (provider, encrypted API key, base URL, model, debug flag) lives on the `users` table; managed via `GET/PUT/DELETE /settings/ai`
- `ai_api_key_enc` is AES-256-GCM encrypted with `encrypt_field`/`decrypt_field`