Mikro ERP Vadesinde Ödeme Yüzdesi Analizi: Cari Ödeme Disiplini SQL Raporu

Müşterileriniz faturalarını vadesinde mi ödüyor? Mikro ERP'de vadesinde ödeme yüzdesi, ağırlıklı vade gecikme günü ve aylık trend analizi SQL sorgusu. Parça bazlı FIFO eşleştirme ile gerçek performans ölçümü.

Vadesinde Ödeme Yüzdesi Neden Bu Kadar Önemli?

“Bu müşteriye vadeli satış yapmalı mıyız?” sorusunun cevabını veren en güçlü metriklerden biri vadesinde ödeme yüzdesidir.

Vadesinde ÖdemeYorumAksiyon
%80 ve üzeriİyi ödeme disipliniVade süresi uzatılabilir
%60 — %80Takip gerektirenStandart vade, yakın izleme
%40 — %60RiskliVade kısaltılmalı veya teminat istenmeli
%40 altıYüksek riskPeşin satış veya satış durdurma

Bankalar kredi derecelendirmesinde, sigorta şirketleri alacak sigortasında bu metriği kullanır. ERP’nizden çekeceğiniz bu veri, kendi iç kredi derecelendirme sisteminizin temel taşıdır.

Basit Yaklaşım: Fatura Bazlı Kontrol

İlk adım olarak, vadeli faturalarınızı ve ödeme durumlarını görelim:

DECLARE @CariKod VARCHAR(50) = '120.001'; -- Kendi cari kodunuzu yazin

SELECT
    ch.cha_Guid,
    CAST(ch.cha_tarihi AS date) AS [Fatura Tarihi],
    ch.cha_meblag AS [Tutar],
    ch.cha_evrakno_seri + '-' + CAST(ch.cha_evrakno_sira AS VARCHAR) AS [Evrak No],
    -- Vade tarihi cozumleme
    CASE
        WHEN ch.cha_vade BETWEEN 19000101 AND 20991231
        THEN TRY_CONVERT(date, CONVERT(char(8), ch.cha_vade), 112)
        WHEN ch.cha_vade BETWEEN -36500 AND -1
        THEN DATEADD(DAY, ABS(ch.cha_vade), CAST(ch.cha_tarihi AS date))
        ELSE NULL
    END AS [Vade Tarihi],
    -- Vadesinin dolup dolmadigi
    CASE
        WHEN CASE
            WHEN ch.cha_vade BETWEEN 19000101 AND 20991231
            THEN TRY_CONVERT(date, CONVERT(char(8), ch.cha_vade), 112)
            WHEN ch.cha_vade BETWEEN -36500 AND -1
            THEN DATEADD(DAY, ABS(ch.cha_vade), CAST(ch.cha_tarihi AS date))
            ELSE NULL
        END < GETDATE() THEN 'Vadesi Doldu'
        ELSE 'Vadesi Gelmedi'
    END AS [Vade Durumu]
FROM CARI_HESAP_HAREKETLERI ch WITH (NOLOCK)
WHERE ch.cha_kod = @CariKod
  AND ch.cha_tip = 0          -- Borc (fatura)
  AND ch.cha_iptal = 0
  AND ch.cha_meblag > 0
ORDER BY ch.cha_tarihi ASC;

Ancak bu basit sorgu sadece faturaları listeler — “vadesinde ödendi mi?” sorusuna cevap veremez çünkü hangi tahsilatın hangi faturayı kapattığını bilmez.

Profesyonel Yaklaşım: FIFO Parça Bazlı Analiz

Vadesinde ödeme yüzdesini doğru hesaplamak için her tahsilatın hangi faturanın ne kadarını kapattığını bulmamız gerekir. FIFO kesişim algoritmasını kullanıyoruz:

DECLARE @CariKod VARCHAR(50) = '120.001';

;WITH Borclar AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY cha_tarihi ASC, cha_create_date ASC) AS SiraNo,
        CAST(cha_tarihi AS date) AS FaturaTarihi,
        cha_meblag AS Tutar,
        cha_evrakno_seri + '-' + CAST(cha_evrakno_sira AS VARCHAR) AS EvrakNo,
        CASE
            WHEN cha_vade BETWEEN 19000101 AND 20991231
            THEN TRY_CONVERT(date, CONVERT(char(8), cha_vade), 112)
            WHEN cha_vade BETWEEN -36500 AND -1
            THEN DATEADD(DAY, ABS(cha_vade), CAST(cha_tarihi AS date))
            ELSE NULL
        END AS VadeTarihi
    FROM CARI_HESAP_HAREKETLERI WITH (NOLOCK)
    WHERE cha_kod = @CariKod
      AND cha_tip = 0 AND cha_iptal = 0 AND cha_meblag > 0
),
Alacaklar AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY cha_tarihi ASC, cha_create_date ASC) AS SiraNo,
        CAST(cha_tarihi AS date) AS OdemeTarihi,
        cha_meblag AS Tutar
    FROM CARI_HESAP_HAREKETLERI WITH (NOLOCK)
    WHERE cha_kod = @CariKod
      AND cha_tip = 1 AND cha_iptal = 0 AND cha_meblag > 0
),
BorcCum AS (
    SELECT *, SUM(Tutar) OVER (ORDER BY SiraNo
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CumTutar
    FROM Borclar
),
AlacakCum AS (
    SELECT *, SUM(Tutar) OVER (ORDER BY SiraNo
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CumTutar
    FROM Alacaklar
),
Kesisim AS (
    SELECT
        b.SiraNo AS FaturaSiraNo,
        b.FaturaTarihi,
        b.VadeTarihi,
        b.EvrakNo,
        a.OdemeTarihi,
        -- Kesisim tutari (FIFO parca)
        CASE
            WHEN (CASE WHEN b.CumTutar < a.CumTutar
                       THEN b.CumTutar ELSE a.CumTutar END)
               - (CASE WHEN b.CumTutar - b.Tutar > a.CumTutar - a.Tutar
                       THEN b.CumTutar - b.Tutar
                       ELSE a.CumTutar - a.Tutar END) > 0
            THEN (CASE WHEN b.CumTutar < a.CumTutar
                       THEN b.CumTutar ELSE a.CumTutar END)
               - (CASE WHEN b.CumTutar - b.Tutar > a.CumTutar - a.Tutar
                       THEN b.CumTutar - b.Tutar
                       ELSE a.CumTutar - a.Tutar END)
            ELSE 0
        END AS KapananParca
    FROM BorcCum b
    INNER JOIN AlacakCum a
        ON a.CumTutar > b.CumTutar - b.Tutar
        AND a.CumTutar - a.Tutar < b.CumTutar
)

-- SONUC: Vadesinde odeme analizi
SELECT
    @CariKod AS [Cari Kodu],

    -- Toplam vadeli kapanan tutar
    SUM(CASE WHEN VadeTarihi IS NOT NULL
             THEN KapananParca ELSE 0 END)
        AS [Vadeli Kapanan Tutar],

    -- Vadesinde odenen parcalar
    SUM(CASE WHEN VadeTarihi IS NOT NULL
              AND OdemeTarihi <= VadeTarihi
             THEN KapananParca ELSE 0 END)
        AS [Vadesinde Odenen Tutar],

    -- VADESINDE ODEME YUZDESI
    CONVERT(decimal(5,1),
    100.0 * SUM(CASE WHEN VadeTarihi IS NOT NULL
                      AND OdemeTarihi <= VadeTarihi
                     THEN KapananParca ELSE 0 END)
          / NULLIF(SUM(CASE WHEN VadeTarihi IS NOT NULL
                            THEN KapananParca ELSE 0 END), 0))
        AS [Vadesinde Odeme Yuzdesi],

    -- Geciken parcalarin ortalama gecikme gunu
    CONVERT(decimal(10,1),
    SUM(CASE WHEN VadeTarihi IS NOT NULL
              AND OdemeTarihi > VadeTarihi
             THEN KapananParca * DATEDIFF(DAY, VadeTarihi, OdemeTarihi)
             ELSE 0 END)
    / NULLIF(SUM(CASE WHEN VadeTarihi IS NOT NULL
                       AND OdemeTarihi > VadeTarihi
                      THEN KapananParca ELSE 0 END), 0))
        AS [Ort Vade Gecikme Gun]

FROM Kesisim
WHERE KapananParca > 0;

SQL Server 2022 kullananlar için: CASE WHEN b.CumTutar < a.CumTutar THEN b.CumTutar ELSE a.CumTutar END yerine LEAST(b.CumTutar, a.CumTutar), benzer şekilde GREATEST() kullanabilirsiniz.

Beklenen Output

Cari KoduVadeli KapananVadesinde ÖdenenVadesinde %Ort Gecikme
120.001485.000342.00070,5%18,3 gün

Bu müşteri faturalarının %70,5’ini vadesinde ödemiş, geciken faturalarda ortalama 18,3 gün gecikme yaşanmış.

Aylık Trend Raporu: Ödeme Disiplini Zaman Serisi

Müşterinin ödeme disiplini iyileşiyor mu yoksa kötüleşiyor mu? Aylık trend raporu ile takip edin:

-- Aylik trend: vadesinde odeme yuzdesi zaman serisi
-- (Yukaridaki CTE'leri aynen kullanin, sadece final SELECT degisir)

SELECT
    FORMAT(FaturaTarihi, 'yyyy-MM') AS [Ay],
    COUNT(DISTINCT FaturaSiraNo) AS [Fatura Sayisi],
    CONVERT(decimal(18,0), SUM(KapananParca)) AS [Kapanan Tutar],

    -- Vadesinde odeme yuzdesi (o ayin faturalari icin)
    CONVERT(decimal(5,1),
    100.0 * SUM(CASE WHEN VadeTarihi IS NOT NULL
                      AND OdemeTarihi <= VadeTarihi
                     THEN KapananParca ELSE 0 END)
          / NULLIF(SUM(CASE WHEN VadeTarihi IS NOT NULL
                            THEN KapananParca ELSE 0 END), 0))
        AS [Vadesinde Odeme %],

    -- O ayin ortalama gecikme gunu
    CONVERT(decimal(10,1),
    SUM(CASE WHEN VadeTarihi IS NOT NULL
              AND OdemeTarihi > VadeTarihi
             THEN KapananParca * DATEDIFF(DAY, VadeTarihi, OdemeTarihi)
             ELSE 0 END)
    / NULLIF(SUM(CASE WHEN VadeTarihi IS NOT NULL
                       AND OdemeTarihi > VadeTarihi
                      THEN KapananParca ELSE 0 END), 0))
        AS [Ort Gecikme Gun]

FROM Kesisim
WHERE KapananParca > 0
GROUP BY FORMAT(FaturaTarihi, 'yyyy-MM')
ORDER BY [Ay];

Beklenen Trend Output

AyFatura SayısıKapanan TutarVadesinde %Ort Gecikme
2025-075120.00085,0%8 gün
2025-08495.00078,2%12 gün
2025-096140.00072,1%15 gün
2025-105110.00065,0%22 gün
2025-11488.00055,3%28 gün
2025-127165.00048,0%35 gün

🔴 Erken uyarı sinyali! Vadesinde ödeme yüzdesi 85%‘ten 48%‘e düşmüş, gecikme günü 8’den 35’e çıkmış. Bu müşterinin kredi limiti gözden geçirilmeli.

Sonuçları İş Kararlarına Çevirme

Vadesinde ödeme yüzdesi tek başına bir sayıdır. Anlamlı hale getirmek için:

Karar Matrisi

Vadesinde %TrendKarar
>80%Sabit/Yükselen✅ Güvenli — vade uzatılabilir
>80%Düşen⚠️ İzle — henüz iyi ama bozuluyor
60-80%Sabit🟡 Standart vade, teminat öner
60-80%Düşen🔴 Vade kısalt, teminat iste
<60%Herhangi🔴 Peşin satış veya satış durdur

Kredi Derecelendirme Entegrasyonu

Bu metrik, cari derecelendirme skorunun temel bileşenlerinden biridir. Detaylı skorlama modeli için: Cari Kredi Derecelendirme ve Skorlama

Nihai Tam Sorgu: Kopyala — Yapıştır — Çalıştır

Vadesinde ödeme yüzdesi + aylık trend tek sorguda:

-- =============================================
-- VADESINDE ODEME YUZDESI + AYLIK TREND
-- Kullanim: @CariKod degistirin, F5 basin
-- Uyumluluk: SQL Server 2016+
-- =============================================
DECLARE @CariKod VARCHAR(50) = '120.001'; -- << BURAYA KENDI CARI KODUNUZU YAZIN

IF OBJECT_ID('tempdb..#Kesisim') IS NOT NULL DROP TABLE #Kesisim;

;WITH Borclar AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY cha_tarihi ASC, cha_create_date ASC) AS SiraNo,
        CAST(cha_tarihi AS date) AS FaturaTarihi,
        cha_meblag AS Tutar,
        CASE
            WHEN cha_vade BETWEEN 19000101 AND 20991231
            THEN TRY_CONVERT(date, CONVERT(char(8), cha_vade), 112)
            WHEN cha_vade BETWEEN -36500 AND -1
            THEN DATEADD(DAY, ABS(cha_vade), CAST(cha_tarihi AS date))
            ELSE NULL
        END AS VadeTarihi
    FROM CARI_HESAP_HAREKETLERI WITH (NOLOCK)
    WHERE cha_kod = @CariKod
      AND cha_tip = 0 AND cha_iptal = 0 AND cha_meblag > 0
),
Alacaklar AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY cha_tarihi ASC, cha_create_date ASC) AS SiraNo,
        CAST(cha_tarihi AS date) AS OdemeTarihi,
        cha_meblag AS Tutar
    FROM CARI_HESAP_HAREKETLERI WITH (NOLOCK)
    WHERE cha_kod = @CariKod
      AND cha_tip = 1 AND cha_iptal = 0 AND cha_meblag > 0
),
BorcCum AS (
    SELECT *, SUM(Tutar) OVER (ORDER BY SiraNo
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CumTutar
    FROM Borclar
),
AlacakCum AS (
    SELECT *, SUM(Tutar) OVER (ORDER BY SiraNo
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CumTutar
    FROM Alacaklar
)

SELECT
    b.SiraNo AS FaturaSiraNo, b.FaturaTarihi, b.VadeTarihi,
    a.OdemeTarihi,
    CASE
        WHEN (CASE WHEN b.CumTutar < a.CumTutar
                   THEN b.CumTutar ELSE a.CumTutar END)
           - (CASE WHEN b.CumTutar - b.Tutar > a.CumTutar - a.Tutar
                   THEN b.CumTutar - b.Tutar
                   ELSE a.CumTutar - a.Tutar END) > 0
        THEN (CASE WHEN b.CumTutar < a.CumTutar
                   THEN b.CumTutar ELSE a.CumTutar END)
           - (CASE WHEN b.CumTutar - b.Tutar > a.CumTutar - a.Tutar
                   THEN b.CumTutar - b.Tutar
                   ELSE a.CumTutar - a.Tutar END)
        ELSE 0
    END AS KapananParca
INTO #Kesisim
FROM BorcCum b
INNER JOIN AlacakCum a
    ON a.CumTutar > b.CumTutar - b.Tutar
    AND a.CumTutar - a.Tutar < b.CumTutar;

-- SONUC 1: Genel ozet
SELECT
    @CariKod AS [Cari Kodu],
    CONVERT(decimal(18,0),
        SUM(CASE WHEN VadeTarihi IS NOT NULL THEN KapananParca ELSE 0 END))
        AS [Vadeli Kapanan Tutar],
    CONVERT(decimal(5,1),
        100.0 * SUM(CASE WHEN VadeTarihi IS NOT NULL AND OdemeTarihi <= VadeTarihi
                         THEN KapananParca ELSE 0 END)
              / NULLIF(SUM(CASE WHEN VadeTarihi IS NOT NULL
                                THEN KapananParca ELSE 0 END), 0))
        AS [Vadesinde Odeme %],
    CONVERT(decimal(10,1),
        SUM(CASE WHEN VadeTarihi IS NOT NULL AND OdemeTarihi > VadeTarihi
                 THEN KapananParca * DATEDIFF(DAY, VadeTarihi, OdemeTarihi) ELSE 0 END)
        / NULLIF(SUM(CASE WHEN VadeTarihi IS NOT NULL AND OdemeTarihi > VadeTarihi
                          THEN KapananParca ELSE 0 END), 0))
        AS [Ort Gecikme Gun]
FROM #Kesisim WHERE KapananParca > 0;

-- SONUC 2: Aylik trend
SELECT
    FORMAT(FaturaTarihi, 'yyyy-MM') AS [Ay],
    COUNT(DISTINCT FaturaSiraNo) AS [Fatura Sayisi],
    CONVERT(decimal(5,1),
        100.0 * SUM(CASE WHEN VadeTarihi IS NOT NULL AND OdemeTarihi <= VadeTarihi
                         THEN KapananParca ELSE 0 END)
              / NULLIF(SUM(CASE WHEN VadeTarihi IS NOT NULL
                                THEN KapananParca ELSE 0 END), 0))
        AS [Vadesinde %],
    CONVERT(decimal(10,1),
        SUM(CASE WHEN VadeTarihi IS NOT NULL AND OdemeTarihi > VadeTarihi
                 THEN KapananParca * DATEDIFF(DAY, VadeTarihi, OdemeTarihi) ELSE 0 END)
        / NULLIF(SUM(CASE WHEN VadeTarihi IS NOT NULL AND OdemeTarihi > VadeTarihi
                          THEN KapananParca ELSE 0 END), 0))
        AS [Ort Gecikme Gun]
FROM #Kesisim WHERE KapananParca > 0
GROUP BY FORMAT(FaturaTarihi, 'yyyy-MM')
ORDER BY [Ay];

DROP TABLE #Kesisim;

Edge Cases (Dikkat Edilmesi Gerekenler)

1. Vadesiz Faturalar (VadeTarihi = NULL)

Mikro’da bazı faturalarda vade tarihi girilmemiş olabilir (cha_vade = 0 veya boş). Bu faturalar vadesinde ödeme analizine dahil edilmez — çünkü karşılaştırma yapılacak bir vade tarihi yoktur.

Eğer vadesiz faturaları da dahil etmek isterseniz, varsayılan bir vade günü atayabilirsiniz:

-- Vadesiz faturalara 30 gun varsayilan vade ata
ISNULL(VadeTarihi, DATEADD(DAY, 30, FaturaTarihi)) AS VadeTarihi

2. Çek/Senet ile Ödeme

Çek alındığı tarih (cha_tarihi) vade tarihinden önce olabilir ama gerçek tahsilat çekin vadesinde gerçekleşir. Bu durumda “vadesinde ödendi” kararı hangi tarihe göre verilecek?

Detaylı analiz için: Çek ve Senet Gerçek Tahsilat Tarihi

3. Kısmi Ödemeler

Bir faturanın bir kısmı vadesinden önce, bir kısmı vadesinden sonra ödenmiş olabilir. FIFO parça bazlı analiz bunu otomatik olarak ele alır — her parça ayrı ayrı değerlendirilir.

İlgili Yazılar


Bu yazı AstaFlow Case Study serisinin bir parçasıdır — vadesinde ödeme yüzdesi, cari risk derecelendirme modülünün temel girdilerinden biridir.

📚 İlgili Yazılar