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 Ödeme | Yorum | Aksiyon |
|---|---|---|
| %80 ve üzeri | İyi ödeme disiplini | Vade süresi uzatılabilir |
| %60 — %80 | Takip gerektiren | Standart vade, yakın izleme |
| %40 — %60 | Riskli | Vade kısaltılmalı veya teminat istenmeli |
| %40 altı | Yüksek risk | Peş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 ENDyerineLEAST(b.CumTutar, a.CumTutar), benzer şekildeGREATEST()kullanabilirsiniz.
Beklenen Output
| Cari Kodu | Vadeli Kapanan | Vadesinde Ödenen | Vadesinde % | Ort Gecikme |
|---|---|---|---|---|
| 120.001 | 485.000 | 342.000 | 70,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
| Ay | Fatura Sayısı | Kapanan Tutar | Vadesinde % | Ort Gecikme |
|---|---|---|---|---|
| 2025-07 | 5 | 120.000 | 85,0% | 8 gün |
| 2025-08 | 4 | 95.000 | 78,2% | 12 gün |
| 2025-09 | 6 | 140.000 | 72,1% | 15 gün |
| 2025-10 | 5 | 110.000 | 65,0% | 22 gün |
| 2025-11 | 4 | 88.000 | 55,3% | 28 gün |
| 2025-12 | 7 | 165.000 | 48,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 % | Trend | Karar |
|---|---|---|
| >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
- Ortalama Tahsilat Süresi (DSO) Hesaplama
- FIFO Fatura-Tahsilat Eşleştirme Algoritması
- Çek ve Senet Gerçek Tahsilat Tarihi
- Cari Kredi Derecelendirme ve Skorlama
- Cari Risk Raporu — Bakiye, Sipariş, Teminat
- Cari Yaşlandırma SQL Sorgusu — FIFO Bakiye Dağıtımı
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.