▶️ Senaryo: Hisse Aldım (SVGYO 1M lot @ ₺21.22) — Ne Olur?
Portföyüme bir al işlemi eklediğimde backend + frontend + 4 modülde ne tetiklenir?
🎬 Adım Adım Akış (12 Adım)
- UI: Portföy detay sayfasında "İşlem Ekle" butonu → Modal açılır
- Form alanları:
- Sembol arama (autocomplete:
/api/stocks/search?q=SVG) - İşlem tipi (Al / Sat)
- Adet (1,005,999)
- Fiyat (₺21.22)
- Tarih (12 Mart 2026)
- Komisyon (opsiyonel)
- Sembol arama (autocomplete:
- "Kaydet" tıklanır:
POST /api/portfolio/trade { "portfolioId": 23919, "sembol": "SVGYO", "tip": "al", "adet": 1005999, "fiyat": 21.22, "tarih": "2026-03-12T10:30:00Z", "komisyon": 0 } - Backend validasyon + hesaplama:
- Auth check
- Sembol exist mi? (
SELECT * FROM stocks WHERE hisseKodu='SVGYO') - Portföy sahibi mi? (
kullaniciId = 80 matches) - Ağırlıklı ortalama maliyet recalc:
yeniOrtMaliyet = (mevcutAdet × mevcutOrtMaliyet + yeniAdet × yeniFiyat) / (mevcutAdet + yeniAdet)
- Cüzdan bakiye kontrol: ₺978M ≥ ₺21.3M ✓
- Database TX (atomic):
BEGIN; INSERT INTO trades (portfolio_id, sembol, tip, adet, fiyat, tarih); UPDATE positions SET ort_maliyet=?, adet=?, toplam_maliyet=? WHERE portfolio_id=23919 AND sembol='SVGYO'; INSERT INTO wallet_transactions (user_id, tip, tutar, kategori, aciklama) VALUES (80, 'cikis', -21300000, 'Portföy', 'Portföy alım: SVGYO'); UPDATE wallet_balance SET tutar = tutar - 21300000 WHERE user_id=80; COMMIT; - Domain Events publish (4 event):
EventBus.emit('trade:changed', { tradeId, portfolioId, sembol }) EventBus.emit('portfolio:changed', { portfolioId, action: 'position_added' }) EventBus.emit('wallet:transaction', { userId, amount: -21300000 }) EventBus.emit('balance:changed', { userId, newBalance }) - WebSocket Gateway broadcast: Tüm kullanıcı tab'larına 4 event push edilir.
- Frontend WS handler:
socket.on('portfolio:changed', () => { queryClient.invalidateQueries(['portfolio', 23919]) queryClient.invalidateQueries(['portfolio']) }) socket.on('trade:changed', () => { queryClient.invalidateQueries(['portfolio', 23919, 'trades']) }) socket.on('wallet:transaction', () => { queryClient.invalidateQueries(['wallet']) }) - React Query bulk refetch (paralel):
GET /api/portfolio/23919 → güncel değerler GET /api/portfolio/23919/holdings → SVGYO ekli GET /api/portfolio/23919/trades → yeni işlem GET /api/wallet/balance → bakiye düşmüş GET /api/wallet/transactions → yeni gider - UI eş zamanlı refresh (3 sayfa):
- Portföy detay → Pozisyon ekli, K/Z hesaplanmış
- Cüzdan → Bakiye düşmüş, işlem listede
- Sidebar widget → Portföy değer güncel
- Real-time fiyat tick: SVGYO için Stock WS subscribe edilir
stockSocket.emit('subscribe', { symbols: ['SVGYO', 'LXGYO', 'SMRVA'] }) - Modal kapanır + success toast: "Alım işlemi başarıyla eklendi: 1,005,999 SVGYO @ ₺21.22"
💰 Etkilenen Modüller (5)
| Modül | Etki | Endpoint |
|---|---|---|
| Portföy | Yeni pozisyon + maliyet recalc | POST /trade, GET /holdings |
| Cüzdan | Otomatik gider kaydı + bakiye düş | GET /wallet/balance, /transactions |
| WebSocket | 4 domain event broadcast | trade/portfolio/wallet/balance :changed |
| Bildirimler | Toast notification | (client-side) |
| PostHog | Event capture: trade_added | analytics.fvt.com.tr/e/ |
🔄 Veri Akış Diyagramı
[User: "İşlem Ekle" → SVGYO 1M @ ₺21.22]
│
▼
[POST /api/portfolio/trade]
│
▼
┌─────────────────────────────────────┐
│ BACKEND TRANSACTION (atomic) │
│ ┌───────────────────────────────┐ │
│ │ INSERT trades │ │
│ │ UPDATE positions (ort.maliyet) │ │
│ │ INSERT wallet_transactions │ │
│ │ UPDATE wallet_balance │ │
│ └───────────────────────────────┘ │
└──────────────┬──────────────────────┘
│
├──→ [Domain Event: trade:changed]
├──→ [Domain Event: portfolio:changed]
├──→ [Domain Event: wallet:transaction]
└──→ [Domain Event: balance:changed]
│
▼
[Event Bus (Redis Pub/Sub)]
│
▼
[WS Gateway broadcast]
│
┌───────────┼────────────┐
▼ ▼ ▼
[Tab 1] [Tab 2] [Tab 3]
│ │ │
└───────────┴────────────┘
│
▼
[React Query invalidate]
│
▼
[3 sayfa eş zamanlı refresh]
- /portfoy/23919
- /cuzdan
- /portfoy (hub)
🧮 Maliyet Hesabı Detay (canlı kanıtlandı)
İlk işlem: SVGYO 5 lot @ ₺410
İkinci işlem: SVGYO 5 lot @ ₺500
Yeni Ortalama = (5 × 410 + 5 × 500) / (5 + 5)
= (2,050 + 2,500) / 10
= 4,550 / 10
= ₺455.00 ✓
🎯 Kullanıcı Deneyimi
- Modal'a sembol yaz → autocomplete
- Adet/fiyat gir
- "Kaydet" tıkla
- ~1 saniye içinde: 3 sayfada eş zamanlı güncelleme
- Modal kapanır + success message
⚡ Performans
Tek POST sonrası 4 domain event + WS broadcast + React Query bulk refetch
= ~500ms-1sn total user-perceived latency.
🗄️ Etkilenen DB Tabloları
| Tablo | İşlem | Kolonlar |
|---|---|---|
trades | INSERT | id, portfoy_id, sembol, islem_tipi='AL', adet, fiyat, komisyon, islem_tarihi, para_birimi |
positions | UPSERT | id, portfoy_id, sembol, adet, ort_maliyet, son_guncelleme |
portfolio_snapshots | INSERT | id, portfoy_id, snapshot_tarihi, toplam_deger, nakit, varlik_dagilim |
wallet_transactions | INSERT | id, kullanici_id, tip='gider', tutar, kategori='hisse_alim', referans_id |
domain_events_log | INSERT | event_tipi='trade:changed' |
portfolio_ai_analysis_cache | ⚠️ Invalidate edilmiyor (Bug #24) | — |
📖 Trade/Position tabloları tam şema → · ⚠️ Cache invalidation bug →