▶️ 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)

  1. UI: Portföy detay sayfasında "İşlem Ekle" butonu → Modal açılır
  2. 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)
  3. "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
    }
  4. 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 ✓
  5. 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;
  6. 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 })
  7. WebSocket Gateway broadcast: Tüm kullanıcı tab'larına 4 event push edilir.
  8. 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'])
    })
  9. 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
  10. 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
  11. Real-time fiyat tick: SVGYO için Stock WS subscribe edilir
    stockSocket.emit('subscribe', { symbols: ['SVGYO', 'LXGYO', 'SMRVA'] })
  12. Modal kapanır + success toast: "Alım işlemi başarıyla eklendi: 1,005,999 SVGYO @ ₺21.22"

💰 Etkilenen Modüller (5)

ModülEtkiEndpoint
PortföyYeni pozisyon + maliyet recalcPOST /trade, GET /holdings
CüzdanOtomatik gider kaydı + bakiye düşGET /wallet/balance, /transactions
WebSocket4 domain event broadcasttrade/portfolio/wallet/balance :changed
BildirimlerToast notification(client-side)
PostHogEvent capture: trade_addedanalytics.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

  1. Modal'a sembol yaz → autocomplete
  2. Adet/fiyat gir
  3. "Kaydet" tıkla
  4. ~1 saniye içinde: 3 sayfada eş zamanlı güncelleme
  5. 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İşlemKolonlar
tradesINSERTid, portfoy_id, sembol, islem_tipi='AL', adet, fiyat, komisyon, islem_tarihi, para_birimi
positionsUPSERTid, portfoy_id, sembol, adet, ort_maliyet, son_guncelleme
portfolio_snapshotsINSERTid, portfoy_id, snapshot_tarihi, toplam_deger, nakit, varlik_dagilim
wallet_transactionsINSERTid, kullanici_id, tip='gider', tutar, kategori='hisse_alim', referans_id
domain_events_logINSERTevent_tipi='trade:changed'
portfolio_ai_analysis_cache⚠️ Invalidate edilmiyor (Bug #24)

📖 Trade/Position tabloları tam şema → · ⚠️ Cache invalidation bug →