---
title: "Mikro ERP Alış-Satış Kâr Analizi: Ürün Bazlı Brüt Kâr/Zarar SQL Sorgusu"
description: "Mikro ERP'de brüt kâr nasıl garantili hesaplanır? Alış maliyeti ve satış gelirini karşılaştıran CTE analizleri, bedelsiz/promosyon ürünlerinin kârlılık..."
date: 2026-04-15
category: mikro-erp
tags: ["mikro-erp", "sql-server", "kar-analizi", "brut-kar", "maliyet", "stok", "astaflow"]
url: https://mikroerp.dev/blog/mikro-erp-alis-satis-kar-analizi-sql/
---

## İş Problemi: "Sattıkça Neden Para Kaybediyoruz?"

Patronların ERP raporlarında en sık tartışma çıkardığı tablo "Brüt Kâr" raporudur. Yönetim paneli, bir ürünün %35 kârla satıldığını söyler ancak yıl sonu mizanları bambaşka bir tablo çıkartır. Sorun genelde SQL'in yanlış yazılmasıdır.

**Neden Standart Kâr Yaklaşımı Çalışmaz?**
Çünkü basit bir "Ciro - Ortalama Alış" denklemi şu durumları hesaba katmaz:
1. **Promosyon/Bedelsiz Alım:** Tedarikçiden 10 adet telefonu normal fiyata, 1 adedini bedelsiz hediye (`sth_tutar = 0`) aldınız. Satışta bu hediyeyi de normal fiyattan satarsınız. Eğer bedelsiz faturayı maliyet ortalamasına dahil ederseniz (sayısal olarak maliyeti sıfıra çekeceğinden) o ürünün birim maliyeti yalan yanlış bir seviyeye düşer.
2. **Müşteri İadeleri:** Müşteri ürünü iade ettiğinde (`sth_normal_iade = 1`), bunu bir "alış" veya "maliyet" kalemi olarak değil, satış cirosundan düşen bir eksi kalem olarak yorumlamalısınız.
3. **Satış İskontoları:** Satır iskontosu mu (Satır toplamı) yoksa evrak altı iskonto mu (Genel Tutar)? Sadece `sth_tutar` alanına bakılmazsa iskonto maliyetleri gözden kaçar.

## Somut Hata: Kârlılık İllüzyonu

Yanlış yazılmış bir SQL sorgusunun vereceği yanıltıcı çıktı:

> **Ürün:** Kırmızı T-Shirt
> **Satış Cirosu:** 10.000 TL
> **Tahmini Maliyet:** 1.000 TL
> **Brüt Kâr Marjı:** %90 (?)
> *Gerçeklik:* Aslında ürünün maliyeti 8.000 TL'ydi. Sadece geçen ay tedarikçi faturayı "sıfır bedelli hediye" kestiği için sistemin maliyet ortalaması dibe çöktü!

## Çözüm: Promosyon ve İade Farkındalıklı CTE Sorgusu

Doğru bir ürün bazlı kâr analizi kurmak için, bedelsiz alımları maliyet hesabından (ama stok sayısından değil!) dışlamak şarttır:

```sql
;WITH SafAlisMaliyeti AS (
    SELECT 
        sth_stok_kod,
        SUM(sth_tutar)  AS [GecerliTutar Toplam],
        -- SADECE Tutar > 0 olan (bedelsiz OLMAYAN) ürünlerin miktarını alıyoruz!
        SUM(CASE WHEN sth_tutar > 0 THEN sth_miktar ELSE 0 END) AS ParaliAlinanMiktar
    FROM STOK_HAREKETLERI WITH (NOLOCK)
    WHERE sth_tip = 0 AND sth_cins = 0 
      AND sth_normal_iade = 0 
      AND sth_iptal = 0
    GROUP BY sth_stok_kod
),
GercekSatisCirosu AS (
    SELECT 
        sth_stok_kod,
        -- İade edilmeyen net satış cirosu (Eğer iade ise cirodan düş!)
        SUM(CASE WHEN sth_normal_iade = 0 THEN sth_tutar ELSE -sth_tutar END) AS NetSatisCirosu,
        -- İade edilmeyen net satış miktarı
        SUM(CASE WHEN sth_normal_iade = 0 THEN sth_miktar ELSE -sth_miktar END) AS NetSatisMiktar
    FROM STOK_HAREKETLERI WITH (NOLOCK)
    WHERE sth_tip = 1 AND sth_cins = 0 
      AND sth_iptal = 0
    GROUP BY sth_stok_kod
)
SELECT 
    SA.sth_stok_kod                                AS [Stok Kodu],
    S.sto_isim                                     AS [Ürün Adı],
    SA.NetSatisMiktar                              AS [Net Satılan Miktar],
    SA.NetSatisCirosu                              AS [Net Satış Cirosu],
    
    -- Gerçekçi (Saf) Ortalama Maliyet Çıkarımı
    ROUND(ISNULL(A.[GecerliTutar Toplam], 0) / 
        NULLIF(A.ParaliAlinanMiktar, 0) * SA.NetSatisMiktar, 2) AS [Kanıtlı Satış Maliyeti],
        
    -- Net Brüt Kâr (Ciro - Kanıtlı Maliyet)
    SA.NetSatisCirosu - ROUND(ISNULL(A.[GecerliTutar Toplam], 0) / 
        NULLIF(A.ParaliAlinanMiktar, 0) * SA.NetSatisMiktar, 2) AS [Net Brüt Kâr],
        
    -- Kâr Marjı (%)
    CAST(ROUND(
        (SA.NetSatisCirosu - ISNULL(A.[GecerliTutar Toplam], 0) / 
            NULLIF(A.ParaliAlinanMiktar, 0) * SA.NetSatisMiktar) / 
            NULLIF(SA.NetSatisCirosu, 0) * 100, 1
    ) AS VARCHAR) + '%'                             AS [Kâr Marjı %],
    
    -- Acil Uyarı Sistemi
    CASE 
        WHEN SA.NetSatisCirosu - ISNULL(A.[GecerliTutar Toplam], 0) / NULLIF(A.ParaliAlinanMiktar, 0) * SA.NetSatisMiktar < 0 
        THEN 'ZARARINA SATIS'
        WHEN ((SA.NetSatisCirosu - ISNULL(A.[GecerliTutar Toplam], 0) / NULLIF(A.ParaliAlinanMiktar, 0) * SA.NetSatisMiktar) / NULLIF(SA.NetSatisCirosu, 0)) < 0.15 
        THEN 'MARJ <%15 (RISK)'
        ELSE 'KARLI ISLEM'
    END                                             AS [Durum Bildirgesi]
    
FROM GercekSatisCirosu SA
INNER JOIN STOKLAR S WITH (NOLOCK) ON SA.sth_stok_kod = S.sto_kod
LEFT JOIN SafAlisMaliyeti A ON SA.sth_stok_kod = A.sth_stok_kod
WHERE SA.NetSatisMiktar > 0 -- Hiç satılmayan (veya sadece iade olan) net miktar varsa listeleme
ORDER BY [Net Brüt Kâr] ASC; -- Özellikle zararları en üstte görmek için!
```

## Performans / Ölçekleme Testi

AstaFlow dashboard mimarisinde, kâr analizlerinin anlık çalışması istenir. Bu sorgu son 1 yıl (`sth_tarih >= DATEADD(YEAR, -1, GETDATE())`) filtresi konulduğunda son derece performanslı çalışır (**0.8 - 1.5 saniye**). Ancak tarihsiz çalıştırılır ve 10 yıllık hareketler taranırsa, RAM üzerinde (Spooling) ciddi yük oluşturur. Kesinlikle tarih limitleri ile uygulanmalıdır.

## Edge Cases (İstisnai Durumlar)

1. **FIFO (İlk Giren İlk Çıkar) Saplantısı:** Yukardaki sorgu *Ağırlıklı Ortalama Maliyet* (Moving Average) yaklaşımıdır. Enflasyonun yüksek olduğu bizim gibi ekonomilerde ortalama maliyet yerine FIFO zorunludur. Ancak saf FIFO hesaplaması SQL'de son derece karmaşık cursor döngüleri gerektirir. Çözüm: Sorguya maliyet hesabını son 3 ayın alımlarıyla sınırlandıran bağımsız bir CTE eklemektir.
2. **Dövizli Alım - TL Satış Kur Makası:** Kâr raporlarındaki gizli katil kur hareketleridir. Ürünü 1000$'a alıp (O günkü kurla 30.000 TL), bugün 35.000 TL'ye saatığınızda SQL size "5000 TL Kâr Gözüküyor" diyecektir. Oysa dolar an itibariyla 34 TL olmuştur (1000$ = 34.000 TL). Gerçek kâr zarara dönmüştür! İleri düzey kâr analizi, geçmiş ciro tutarını o günün kuruyla dövize sabitleyip hesaplanmalıdır.

## Bu Bilgiyi Nereden Biliyoruz? (Kaynaklar)

*   **AstaFlow Case Study:** [Satış Performans ve Ciro Analizi Uygulamaları](/case-study/)
*   **Mikro ERP DB API:** [STOK_HAREKETLERI Tablosu Detayları](https://apidocs.mikro.com.tr/tablo-alan-adlari/stok_hareketleri)
*   **İlgili Çözüm:** [Mikro ERP Son Alış Fiyatı Analizi](/blog/mikro-erp-son-alis-fiyati-sql/)