---
title: "Mikro ERP cha_vade Alanı Nasıl Çalışır? Gerçek Vade Tarihi Hesaplama"
description: "Mikro'da vade neden bir tarih değil de sayı alanı? cha_vade ile gerçek vade hesaplama, ödeme planları ve SARGable olmayan SQL hatalarının çözümü."
date: 2026-04-15
category: mikro-erp
tags: ["mikro-erp", "sql-server", "vade", "odeme-plani", "cari-hesap", "muhasebe", "astaflow"]
url: https://mikroerp.dev/blog/mikro-erp-vade-hesaplama-cha-vade-sql/
---

## İş Problemi: "Vadesi Bugün Dolan Tahsilatlar Neler?"

Haftaya başlarken Finans birimi sizden çok basit bir veri ister: *"Bu ay içerisinde vadesi dolacak olan müşteri borçlarının listesini verin."*
Tahsilat listesi oluşturmak için SQL'e girdiğinizde, faturanın bir "Vade Tarihi" (`cha_vade_tarihi` gibi) kolonu olmadığını fark eder, paniğe kapılırsınız.

Mikro'da **Vade kolonu (`cha_vade`) bir "tarih" (datetime) değil, bir "sayı" (integer) formatındadır.** Ancak bu alan **iki farklı formatta** veri tutabilir.

## cha_vade Alanının İki Yüzü

| Format | Örnek Değer | Anlamı | Vade Tarihi Hesabı |
|--------|------------|--------|-------------------|
| Gün Sayısı | `30` | Fatura tarihinden 30 gün sonra | `cha_tarihi + 30 gün` |
| YYYYMMDD | `20261231` | Doğrudan 31.12.2026 tarihini temsil eder | `TRY_CONVERT(datetime, '20261231', 112)` |
| Sıfır | `0` | Peşin işlem — vade = fatura tarihi | `cha_tarihi` |

Bu ikili yapıyı bilmeden `DATEADD(DAY, cha_vade, cha_tarihi)` yazmak, 20 milyon gün eklenmesine ve **datetime overflow** hatasına yol açar.

## Somut Hata: Raporların Patlaması

> **Acemice Yazılan SQL:**
> `SELECT * FROM CARI_HESAP_HAREKETLERI WHERE cha_vade = '2024-05-15'` 
> **Sonuç:** `Conversion failed` hatası. Çünkü `cha_vade` bir integer alanıdır.

> **İkinci Tuzak:**
> `DATEADD(DAY, cha_vade, cha_tarihi)` 
> **Sonuç:** `datetime overflow` hatası. Çünkü `cha_vade = 20261231` değerinde 20 milyon gün eklenmeye çalışılır.

## Çözüm: Güvenli Vade Tarihi Hesaplama

Aşağıdaki SQL, `cha_vade` alanının her iki formatını da güvenle yakalar ve overflow hatası vermez:

```sql
SELECT 
    H.cha_kod                                       AS [Cari Kodu],
    C.cari_unvan1                                   AS [Cari Unvan],
    
    CONVERT(VARCHAR(10), H.cha_tarihi, 104)         AS [Fatura Tarihi],
    H.cha_vade                                      AS [Vade Degeri],
    
    -- GUVENLI VADE TARIHI HESAPLAMASI
    CONVERT(VARCHAR(10), 
        CASE
            WHEN H.cha_vade = 0 THEN H.cha_tarihi
            WHEN H.cha_vade BETWEEN 1 AND 3650 
            THEN DATEADD(DAY, H.cha_vade, H.cha_tarihi)
            WHEN H.cha_vade BETWEEN 19000101 AND 20991231 
            THEN TRY_CONVERT(datetime, CONVERT(char(8), H.cha_vade), 112)
            ELSE H.cha_tarihi
        END, 104)                                    AS [Hesaplanan Vade Tarihi],
    
    -- Tutar
    H.cha_meblag                                    AS [Acik Borc],
    
    -- Geciken Gun Sayisi
    DATEDIFF(DAY, 
        CASE
            WHEN H.cha_vade = 0 THEN H.cha_tarihi
            WHEN H.cha_vade BETWEEN 1 AND 3650 
            THEN DATEADD(DAY, H.cha_vade, H.cha_tarihi)
            WHEN H.cha_vade BETWEEN 19000101 AND 20991231 
            THEN TRY_CONVERT(datetime, CONVERT(char(8), H.cha_vade), 112)
            ELSE H.cha_tarihi
        END, GETDATE())                              AS [Geciken Gun Sayisi],
    CASE 
        WHEN DATEDIFF(DAY, 
            CASE
                WHEN H.cha_vade = 0 THEN H.cha_tarihi
                WHEN H.cha_vade BETWEEN 1 AND 3650 
                THEN DATEADD(DAY, H.cha_vade, H.cha_tarihi)
                WHEN H.cha_vade BETWEEN 19000101 AND 20991231 
                THEN TRY_CONVERT(datetime, CONVERT(char(8), H.cha_vade), 112)
                ELSE H.cha_tarihi
            END, GETDATE()) > 30 THEN '1 Aydan Fazla Gecikme'
        WHEN DATEDIFF(DAY, 
            CASE
                WHEN H.cha_vade = 0 THEN H.cha_tarihi
                WHEN H.cha_vade BETWEEN 1 AND 3650 
                THEN DATEADD(DAY, H.cha_vade, H.cha_tarihi)
                WHEN H.cha_vade BETWEEN 19000101 AND 20991231 
                THEN TRY_CONVERT(datetime, CONVERT(char(8), H.cha_vade), 112)
                ELSE H.cha_tarihi
            END, GETDATE()) > 0 THEN 'Gecikmede'
        ELSE 'Vadesi Gelmedi'
    END                                              AS [Tahsilat Durumu]
    
FROM CARI_HESAP_HAREKETLERI H WITH (NOLOCK)
LEFT JOIN CARI_HESAPLAR C WITH (NOLOCK) ON H.cha_kod = C.cari_kod

WHERE H.cha_tip = 0                                   -- Borc (Musterinin bize borcu)
  AND H.cha_tpoz = 0                                  -- Islem Acik (Tahsil Edilmemis)
  AND H.cha_iptal = 0
ORDER BY [Hesaplanan Vade Tarihi] ASC;
```

### Kodun Anatomisi

- **`cha_vade = 0`:** Peşin işlem. Vade tarihi = fatura tarihi.
- **`cha_vade BETWEEN 1 AND 3650`:** Gün sayısı formatı (1 gün - ~10 yıl). `DATEADD` ile güvenle hesaplanır.
- **`cha_vade BETWEEN 19000101 AND 20991231`:** YYYYMMDD formatı. `TRY_CONVERT` ile datetime'a dönüştürülür.
- **`ELSE cha_tarihi`:** Bilinmeyen format — fallback olarak fatura tarihine döner.

## Performans / Ölçekleme (SARGable Sorgu Tuzağı)

Burada inanılmaz derecede önemli bir veritabanı mühendisliği kuralı vardır. 
"*Şubat ayı içinde vadesi dolan tahsilatları getir*" demek istediğinizde WHERE bloğuna şunu yazarsınız:
`WHERE DATEADD(DAY, H.cha_vade, H.cha_tarihi) BETWEEN '2024-02-01' AND '2024-02-28'`

Bu sorgu **SARGable** (Search ARGument ABLE) değildir! SQL Server `DATEADD` fonksiyonunu kullandığınız an, tablodaki indexleri kullanamaz ve 4 Milyon satırı RAM'de tek tek hesaplayarak (Table Scan / Index Scan) tarar. AstaFlow sisteminde bu hatanın 13 saniyelik kilitlenmelere yol açtığını tespit ettik.

Çözüm? Raporları bir **Computed Column** (Hesaplanmış Kolon) ile Index içerisine hapsedin veya sadece filtrelenmiş bir tarih yılı (`H.cha_tarihi > '2022-01-01'`) ile veriyi küçülttükten sonra hesaplama kullanın.

## Edge Cases (İstisnai Durumlar)

Vade hesaplaması yaparken şu üç "karanlık noktayı" bilmezseniz hesaplarınız şaşar:

1. **Parçalı Taksitler (Ödeme Planları):** Eğer bir faturanın vadesi 30, 60, 90 gün olarak üçe bölündüyse, Mikro `cha_vade` alanını kullanmaz. Bu veri, `CARI_HAREKET_ODEME_VADELERI` (`cop_` önekiyle) tablosuna taşınır. Bu durumda faturayı baz alıp bu tablodan taksit tarihlerini JOIN etmeniz gerekir.
2. **Çek ve Senetlerin Vade Mantığı:** Çek/senet hareketlerinde de vade `cha_vade` alanında YYYYMMDD formatında tutulur (örn: `20261231`). Güvenli CASE/WHEN yapısı bu formatı da doğru yakalar. Detaylı çek/senet vade takibi için [ilgili yazımıza](/blog/mikro-erp-cek-senet-vade-takip-sql/) bakın.
3. **Sıfır Vade (Peşin İşlem):** `cha_vade = 0` ise bu peşin faturadır, işlem tarihi neyse vade tarihi odur. Güvenli hesaplamada bu durum `CASE WHEN cha_vade = 0 THEN cha_tarihi` ile yakalanır.

## Bu Bilgiyi Nereden Biliyoruz? (Kaynaklar)

*   **AstaFlow Case Study:** [Finansal Tahsilat Akışları ve Yaşlandırma Modülleri](/case-study/)
*   **Mikro ERP DB API:** [CARI_HAREKET_ODEME_VADELERI Tablo Detayları](https://apidocs.mikro.com.tr/tablo-alan-adlari/cari_hareket_odeme_vadeleri)
*   **İlgili Çözüm:** [Cari Yaşlandırma (Aging) Optimizasyonu](/blog/mikro-erp-cari-yaslandirma-sql-sorgusu/)