Neden önemli?
Anadolu Market'in muhasebe ekibinde her pazartesi aynı tören yaşanır: "Gelen" klasörüne düşen 40-50 fatura PDF'i, Excel dökümü ve fiş fotoğrafı tek tek açılır, doğru klasöre sürüklenir, adı "2026-10-26_fatura_01.pdf" biçimine çevrilir, klasörün yedeği alınır ve haftalık özet e-postayla yöneticiye gider. Bu iş yarım gün sürer, sıkıcıdır ve sıkıcı işte hata yapılır. Aynı işi 60 satırlık bir Python betiği birkaç saniyede, her seferinde aynı biçimde yapar. Buna ofis otomasyonu (office automation) ya da iş akışı otomasyonu (workflow automation) denir.
Bu derste gerçek bir otomasyon betiğinin parçalarını kuracaksın: klasör düzenleme, toplu yeniden adlandırma, tarih damgalı yedek, e-posta hazırlama, PDF/Word dokunuşları, zamanlama ve dosya izleme. En önemlisi, otomasyonun güvenli olmasını sağlayan üç alışkanlığı öğreneceksin: kuru çalıştırma (dry-run), loglama ve geri alma. pathlib ve shutil ile ilk tanışmanı Dosya işlemleri ve Standart kütüphane turu derslerinde yapmıştın; burada onları bir iş sürecine dönüştürüyoruz.
| İş | Sıklık | Elle süre | Otomasyona değer mi? |
|---|---|---|---|
| Gelen klasörünü uzantıya göre ayırmak | Her gün | 15 dk | Kesinlikle: kural net, risk düşük |
| Haftalık satış raporunu e-postalamak | Haftada bir | 30 dk | Evet: biçim sabit, veri değişken |
| Yıllık bütçe sunumunu hazırlamak | Yılda bir | 2 gün | Kısmen: yorum insan işi, tablolar otomatik |
| Müşteri şikâyetine cevap yazmak | Her gün | Değişken | Hayır: yargı gerektirir; yalnız taslak hazırla |
Kural basit: sık tekrarlanan, kuralı yazıya dökülebilen ve hatası geri alınabilen işler otomasyonun ilk adaylarıdır.
Güvenli oyun alanı: geçici klasör
Otomasyon kodunu ilk kez gerçek "Belgeler" klasöründe denemek, yeni sürücünün otoyola çıkması gibidir. Her zaman önce geçici bir klasörde (temporary directory) dene. Bu sayfadaki örnekler tarayıcıdaki bellek içi dosya sisteminde çalışır; kendi bilgisayarında da aynı kod geçici klasör kullandığı için gerçek dosyalarına dokunmaz.
import tempfile
from pathlib import Path
kok = Path(tempfile.mkdtemp()) / "Gelen"
kok.mkdir()
for ad in ["Fatura_Ekim.PDF", "satislar.csv", "fis 01.jpg", "notlar.txt", "stok.xlsx"]:
(kok / ad).write_text("örnek içerik", encoding="utf-8")
for dosya in sorted(kok.iterdir()):
print(f"{dosya.name:20} uzantı={dosya.suffix.lower():6} boyut={dosya.stat().st_size} bayt")
Path.iterdir() klasördeki öğeleri, suffix uzantıyı, stat() boyut ve değişiklik zamanını verir. Uzantıyı her zaman .lower() ile karşılaştır: Windows'tan gelen dosyalar sıkça .PDF ya da .JPG olur.
Klasör düzenleme: uzantıya göre taşıma
İlk otomasyonumuz klasik "indirilenler temizliği". Önce bir kural tablosu (uzantı → kategori) kur, sonra her dosya için hedefi hesapla. Kodun iki aşamalı olduğuna dikkat et: önce plan (ne yapılacağını listele), sonra uygula. Bu ayrım dry-run'ı bedavaya getirir.
import tempfile, shutil
from pathlib import Path
KURAL = {".pdf": "Belgeler", ".docx": "Belgeler", ".txt": "Belgeler",
".csv": "Tablolar", ".xlsx": "Tablolar", ".jpg": "Görseller", ".png": "Görseller"}
def plan_yap(kok):
plan = []
for dosya in sorted(p for p in kok.iterdir() if p.is_file()):
kategori = KURAL.get(dosya.suffix.lower(), "Diğer")
plan.append((dosya, kok / kategori / dosya.name))
return plan
def uygula(plan, dry_run=True):
for kaynak, hedef in plan:
etiket = "[DRY-RUN] " if dry_run else ""
print(f"{etiket}{kaynak.name} -> {hedef.parent.name}/")
if not dry_run:
hedef.parent.mkdir(parents=True, exist_ok=True)
shutil.move(kaynak, hedef)
kok = Path(tempfile.mkdtemp())
for ad in ["Fatura_Ekim.PDF", "satislar.csv", "fis01.jpg", "README"]:
(kok / ad).write_text("x")
uygula(plan_yap(kok)) # önce yalnızca göster
uygula(plan_yap(kok), dry_run=False)
print(sorted(str(p.relative_to(kok)) for p in kok.rglob("*") if p.is_file()))
mkdir(parents=True, exist_ok=True) ara klasörleri de oluşturur ve klasör zaten varsa hata vermez. shutil.move farklı disk bölümleri arasında da çalışır; Path.rename ise aynı bölüm içinde daha hızlıdır ama Windows'ta hedef varsa hata verir.
Toplu yeniden adlandırma
Fiş fotoğrafları "IMG_4821.JPG", "Fiş - Şubat (son).jpg" gibi adlarla gelir. Arşiv için tutarlı bir ad kuralı belirle: tarih öneki + Türkçe karakter ve boşluk içermeyen gövde + sıra numarası. Tarih önekini YYYYMMDD biçiminde yazarsan dosya adına göre sıralama aynı zamanda kronolojik sıralama olur.
import re
TR = str.maketrans("çğıöşüÇĞİÖŞÜ", "cgiosuCGIOSU")
def sade(metin):
metin = metin.translate(TR).lower()
metin = re.sub(r"[^a-z0-9]+", "_", metin)
return metin.strip("_")
adlar = ["Fiş - Şubat (son).JPG", "IMG_4821.jpg", "Kargo İrsaliyesi.jpg"]
for i, ad in enumerate(sorted(adlar), start=1):
govde, _, uzanti = ad.rpartition(".")
yeni = f"20261026_{i:03d}_{sade(govde)}.{uzanti.lower()}"
print(f"{ad:25} -> {yeni}")
hedef.exists()) bak; varsa atla ya da sonuna sayaç ekle. Aksi halde bir dosyanın üzerine sessizce yazarsın ve veri kaybı olur.Tarih damgalı yedek
Bir klasörü değiştirmeden önce yedeğini almak en ucuz sigortadır. Yedek adına zaman damgası (timestamp) koy; eskileri belirli bir sayının üzerinde sil (rotasyon). Testlerde ve örneklerde sabit bir tarih kullan ki sonuç her çalıştırmada aynı olsun; gerçek betikte datetime.now() kullanırsın.
import tempfile, shutil
from datetime import datetime
from pathlib import Path
alan = Path(tempfile.mkdtemp())
kaynak = alan / "Muhasebe"
kaynak.mkdir()
(kaynak / "ekim.csv").write_text("tutar\n100\n")
yedekler = alan / "Yedekler"
for gun in [20, 21, 22, 23, 24]:
an = datetime(2026, 10, gun, 18, 0)
hedef = yedekler / f"Muhasebe_{an:%Y%m%d_%H%M}"
shutil.copytree(kaynak, hedef)
# rotasyon: yalnızca en yeni 3 yedek kalsın
hepsi = sorted(p for p in yedekler.iterdir() if p.is_dir())
for eski in hepsi[:-3]:
shutil.rmtree(eski)
print("silindi:", eski.name)
print("kalan:", [p.name for p in sorted(yedekler.iterdir())])
Tek dosyalık arşiv istersen shutil.make_archive(str(hedef), "zip", kaynak) klasörü zip'ler. shutil.copy2 tek dosyayı değiştirilme zamanıyla birlikte kopyalar.
Otomasyonun riskleri: dry-run, log, geri alma
Elle yapılan hata bir dosyayı bozar; otomasyon hatası binlercesini aynı anda bozar. Bu yüzden profesyonel otomasyon betiklerinde üç emniyet kemeri vardır:
- Kuru çalıştırma (dry-run): Betik yapacaklarını yazdırır ama hiçbir dosyaya dokunmaz. Varsayılan mod dry-run olsun; gerçek çalıştırma için açıkça
--uygulagibi bir bayrak iste. - Loglama (logging): Her işlemi zamanıyla birlikte bir log dosyasına yaz. "Dün gece betik ne yaptı?" sorusunun cevabı
printçıktısında değil, log dosyasındadır. - Geri alma (undo): Yapılan her taşımayı (kaynak, hedef) çifti olarak kaydet; ters sırayla uygulayarak işlemi geri al.
import logging, tempfile, shutil, io
from pathlib import Path
akis = io.StringIO()
logging.basicConfig(stream=akis, level=logging.INFO, format="%(levelname)s %(message)s", force=True)
log = logging.getLogger("duzenleyici")
kok = Path(tempfile.mkdtemp())
for ad in ["a.pdf", "b.csv"]:
(kok / ad).write_text("x")
gunluk = [] # geri alma günlüğü
for dosya in sorted(kok.glob("*.*")):
hedef = kok / dosya.suffix[1:].upper() / dosya.name
hedef.parent.mkdir(exist_ok=True)
shutil.move(dosya, hedef)
gunluk.append((dosya, hedef))
log.info("taşındı %s -> %s", dosya.name, hedef.parent.name)
for kaynak, hedef in reversed(gunluk): # GERİ AL
shutil.move(hedef, kaynak)
log.warning("geri alındı %s", kaynak.name)
print(akis.getvalue())
print(sorted(p.name for p in kok.iterdir() if p.is_file()))
logging modülünde mesajı %s yer tutucularıyla ver (f-string değil): seviye kapalıysa metin hiç oluşturulmaz. Gerçek betikte stream yerine filename="otomasyon.log" verirsin.
E-posta: mesajı oluşturmak ve göndermek
E-posta iki ayrı iştir: mesajı oluşturmak (başlıklar, gövde, ekler) ve göndermek (SMTP sunucusuna bağlanmak). Birincisi tamamen email.message.EmailMessage ile yapılır ve tarayıcıda da çalışır; ikincisi ağ ister.
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "rapor@anadolumarket.com.tr"
msg["To"] = ", ".join(["mudur@anadolumarket.com.tr", "finans@anadolumarket.com.tr"])
msg["Subject"] = "Haftalık satış özeti - 43. hafta"
msg.set_content("Merhaba,\n\nBu haftanın özeti ektedir.\n\nOtomasyon Botu")
msg.add_alternative("<h3>Haftalık özet</h3><p>Toplam ciro: <b>182.450 TL</b></p>", subtype="html")
msg.add_attachment("magaza,ciro\nKadıköy,98200\nÇankaya,84250\n", subtype="csv", filename="ozet.csv")
print(msg["Subject"])
print(msg.get_content_type())
print([p.get_content_type() for p in msg.iter_parts()])
print([p.get_filename() for p in msg.iter_attachments()])
set_content düz metin gövdeyi, add_alternative aynı içeriğin HTML sürümünü ekler (istemci hangisini gösterebiliyorsa onu seçer). add_attachment metin için subtype, ikili veri (PDF, resim) için maintype ve subtype ister: msg.add_attachment(pdf_bytes, maintype="application", subtype="pdf", filename="fatura.pdf"). Ek eklenince mesaj otomatik olarak multipart/mixed olur.
Göndermek için smtplib kullanırsın. Bu kod ağ gerektirdiği için burada yalnızca gösterim:
import os, smtplib
kullanici = os.environ["SMTP_KULLANICI"]
sifre = os.environ["SMTP_SIFRE"] # uygulama şifresi, ASLA koda yazma
with smtplib.SMTP_SSL("smtp.gmail.com", 465, timeout=30) as sunucu:
sunucu.login(kullanici, sifre)
sunucu.send_message(msg)
setx SMTP_SIFRE "...", macOS/Linux'ta export SMTP_SIFRE="...". Kurumsal ortamda genellikle şirketin iç SMTP sunucusu (port 587 + starttls()) kullanılır. Denemelerde mesajı göndermek yerine open("taslak.eml", "wb").write(bytes(msg)) ile dosyaya yazıp e-posta istemcinde açabilirsin.PDF ve Word dokunuşları
Ofiste en sık istenen iki iş: birçok PDF'i birleştirmek ve şablon bir Word belgesine veri doldurmak. pypdf ve python-docx bilgisayarında pip install pypdf python-docx ile kurulur; gerçek dosyayla görevler PDF ve Word belgeleriyle otomasyon dersinde, tarayıcıda çalışır.
from pathlib import Path
from pypdf import PdfReader, PdfWriter
yazici = PdfWriter()
for yol in sorted(Path("faturalar").glob("*.pdf")):
okuyucu = PdfReader(yol)
print(yol.name, len(okuyucu.pages), "sayfa")
for sayfa in okuyucu.pages:
yazici.add_page(sayfa)
yazici.write("ekim_faturalari.pdf")
print(PdfReader("ekim_faturalari.pdf").pages[0].extract_text()[:200])from docx import Document
belge = Document()
belge.add_heading("Anadolu Market - Haftalık Rapor", level=1)
belge.add_paragraph("Bu hafta toplam ciro 182.450 TL oldu.")
tablo = belge.add_table(rows=1, cols=2)
tablo.rows[0].cells[0].text, tablo.rows[0].cells[1].text = "Mağaza", "Ciro"
for magaza, ciro in [("Kadıköy", "98.200"), ("Çankaya", "84.250")]:
hucreler = tablo.add_row().cells
hucreler[0].text, hucreler[1].text = magaza, ciro
belge.save("haftalik_rapor.docx")
Word şablonunda "{{musteri}}" gibi yer tutucuları değiştirmek gerekiyorsa Şablonla rapor üretme dersindeki Jinja mantığını docxtpl paketiyle Word'e taşıyabilirsin.
Zamanlama: betik kendiliğinden çalışsın
Betiği her pazartesi elle çalıştırmak otomasyonun yarısıdır. Üç yol var:
| Araç | Nerede | Örnek | Not |
|---|---|---|---|
schedule paketi | Her yerde, Python içinde | schedule.every().monday.at("08:30").do(is) | Betik sürekli açık kalmalı |
| cron | Linux / macOS | 30 8 * * 1 /usr/bin/python3 /opt/rapor.py | Sunucularda standart |
| Görev Zamanlayıcı | Windows | schtasks /create /sc weekly /d MON /st 08:30 /tn Rapor /tr "py C:\is\rapor.py" | Arayüzden de kurulur |
Cron ifadesi beş alandan oluşur: dakika saat gün ay haftagünü. * "her", 1-5 aralık, 8,12 liste, */15 "15'te bir" demektir; haftagünü 0 (ya da 7) pazardır. Yani */15 9-17 * * 1-5 "hafta içi 09:00-17:45 arası çeyrek saatte bir" anlamına gelir.
# pip install schedule
import time, schedule
def haftalik_rapor():
print("rapor hazırlanıyor...")
schedule.every().monday.at("08:30").do(haftalik_rapor)
while True:
schedule.run_pending()
time.sleep(30)
Path(__file__).parent ile mutlak kur, sanal ortamın python'unu tam yoluyla çağır ve çıktıyı log dosyasına yönlendir.Dosya izleme
"Klasöre yeni fatura düşer düşmez işle" senaryosu için iki yol var: watchdog paketi işletim sisteminin bildirimlerini dinler (pip install watchdog); daha basit ve her yerde çalışan yol ise yoklama (polling): belirli aralıkla klasörün anlık görüntüsünü (snapshot) alıp öncekiyle karşılaştırmak.
def anlik(dosyalar):
"""ad -> (boyut, değiştirilme zamanı) sözlüğü; gerçekte Path.stat() ile doldurulur"""
return dict(dosyalar)
once = anlik({"a.pdf": (120, 1000), "b.csv": (40, 1000)})
sonra = anlik({"a.pdf": (150, 1060), "c.jpg": (900, 1061)})
eklenen = sorted(sonra.keys() - once.keys())
silinen = sorted(once.keys() - sonra.keys())
degisen = sorted(ad for ad in once.keys() & sonra.keys() if once[ad] != sonra[ad])
print("eklenen:", eklenen, "| silinen:", silinen, "| değişen:", degisen)
Sözlüklerin keys() görünümleri küme gibi davranır: fark (-) ve kesişim (&) doğrudan çalışır. Yarım kopyalanmış bir dosyayı işlememek için boyutun iki ardışık yoklamada değişmediğini görmeyi beklemek yaygın bir hiledir.
Zamanlanmış bir betik sessizce başarısız olursa, sorunu genellikle bir yönetici "rapor neden gelmedi?" diye sorduğunda öğrenirsin. Bu bölüm iki eksiği kapatıyor: her çalıştırmanın sonunda bir çalışma özeti üretmek ve hata olduğunda bildirim göndermek (Teams/Slack webhook ve e-posta); ardından Windows Görev Zamanlayıcı'nın "neden elle çalışıyor da zamanlanınca çalışmıyor" tuzakları.
Çalışma özeti: her çalıştırma bir kayıt
Betiğin ana gövdesini bir sarmalayıcıya al: başlangıç-bitiş zamanı, işlenen ve atlanan dosya sayısı, uyarılar ve durum tek bir sözlükte toplansın. Bu özet hem loga, hem bildirime, hem de (istersen) bir CSV'ye "çalışma geçmişi" olarak yazılır.
import json, traceback
from datetime import datetime
def calistir(is_fonksiyonu, ad, simdi=datetime.now):
ozet = {"is": ad, "baslangic": simdi().isoformat(timespec="seconds"), "durum": "BAŞARILI",
"islenen": 0, "atlanan": 0, "uyarilar": []}
try:
is_fonksiyonu(ozet) # iş, sayaçları ve uyarıları özete yazar
if ozet["uyarilar"]:
ozet["durum"] = "UYARILI"
except Exception as e:
ozet["durum"] = "HATA"
ozet["hata"] = f"{type(e).__name__}: {e}"
ozet["iz"] = traceback.format_exc(limit=3)
ozet["bitis"] = simdi().isoformat(timespec="seconds")
return ozet
def rapor_isi(ozet):
for ad in ["ocak.xlsx", "subat.xlsx", "~$mart.xlsx"]:
if ad.startswith("~$"): # Excel'in açık dosya kilidi
ozet["atlanan"] += 1
ozet["uyarilar"].append(f"{ad} kilit dosyası, atlandı")
continue
ozet["islenen"] += 1
sabit = lambda: datetime(2024, 11, 4, 8, 30)
ozet = calistir(rapor_isi, "aylik_rapor", sabit)
print(json.dumps({k: v for k, v in ozet.items() if k != "iz"}, ensure_ascii=False))
Bildirim: webhook ve e-posta
Microsoft Teams ve Slack, bir kanala dışarıdan mesaj göndermek için gelen webhook (incoming webhook) adresi verir: bu adrese JSON ile POST atarsın, mesaj kanala düşer. Teams'te yeni yol "Workflows" uygulamasındaki "webhook isteği alındığında kanala gönder" akışıdır; eski Office 365 bağlayıcıları kaldırılmaktadır. Adres bir parola gibidir: bilen herkes kanala mesaj atabilir, bu yüzden ortam değişkeninde tutulur.
import os, json, urllib.request
def webhook_gonder(ozet):
govde = {"text": f"[{ozet['durum']}] {ozet['is']}: {ozet['islenen']} işlendi, "
f"{ozet['atlanan']} atlandı" + (f"\nHata: {ozet['hata']}" if "hata" in ozet else "")}
istek = urllib.request.Request(os.environ["TEAMS_WEBHOOK_URL"], data=json.dumps(govde).encode("utf-8"),
headers={"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(istek, timeout=10) as y:
return y.status
except OSError as e:
print("Bildirim gönderilemedi:", e) # bildirim hatası asıl işi düşürmesin
Kurallar: yalnız durum değişince ya da hata olunca bildir (her başarılı çalıştırmada mesaj atmak, kanalı sessize alınan bir gürültüye çevirir); aynı hatayı her 5 dakikada bir tekrar gönderme (susturma süresi); mesaja kişisel veri ya da tam yığın izi koyma, ayrıntıyı log dosyasının yolunu vererek ilet; bildirim gönderilemezse bunu logla ama asıl işi başarısız sayma. E-posta için derste gördüğün EmailMessage + smtplib kalıbını aynı özet metniyle kullan; ek olarak log dosyasının son 50 satırını ekleyebilirsin.
Windows Görev Zamanlayıcı tuzakları
| Belirti | Neden | Çözüm |
|---|---|---|
| Elle çalışıyor, zamanlanınca "dosya bulunamadı" | Çalışma klasörü C:\Windows\System32 | "Başlangıç yeri" (Start in) alanını doldur; kodda Path(__file__).parent kullan |
| Yalnız oturum açıkken çalışıyor | "Yalnızca kullanıcı oturum açtığında çalıştır" seçili | "Kullanıcı oturum açmış olsa da olmasa da çalıştır"ı seç; parola istenir |
| Oturum kapalıyken ağ sürücüsüne erişemiyor | Z: gibi eşlenmiş sürücüler yalnız etkileşimli oturumda vardır | UNC yolu kullan: \\sunucu\paylasim\rapor; hesaba paylaşım izni ver |
| Parola değişince görev sessizce durdu | Görev, kaydedilmiş parolayla çalışır | Parolası süresiz servis hesabı ya da gMSA kullan; parola değişince görevi güncelle |
| Excel/Outlook otomasyonu takılıyor | Oturum kapalıyken masaüstü uygulaması açılamaz | openpyxl/pandas gibi arayüzsüz kütüphaneler, SMTP ya da Graph API kullan |
| "Son çalıştırma sonucu 0x1" | Betik 1 çıkış koduyla bitti (Python hatası) | Betiğin kendi log dosyasına bak; görev çıktısı hiçbir yere yazılmaz |
| Yanlış Python ya da paket bulunamadı | PATH'teki python farklı | Programa sanal ortamın tam yolunu ver: C:\is\.venv\Scripts\python.exe |
schtasks /create /tn "AnadoluRapor" /sc daily /st 07:45 ^ /tr "C:\is\.venv\Scripts\python.exe C:\is\rapor.py" ^ /ru "ANADOLU\svc_rapor" /rp * /rl LIMITED schtasks /query /tn "AnadoluRapor" /v /fo LIST # son çalıştırma zamanı ve sonucu schtasks /run /tn "AnadoluRapor" # şimdi dene
/ru ve /rp * görevin hangi hesapla, oturum açık olmadan çalışacağını belirler (* parolayı gizli sorar). /rl LIMITED yönetici yetkisi vermez; gerekmedikçe yükseltme. "Ayarlar" sekmesinde "görev zaten çalışıyorsa yeni örnek başlatma" seçeneği, derste gördüğün kilit dosyasını tamamlar.
%USERPROFILE% farklı çözülebilir. Sırları ortam değişkenine görev hesabı için tanımla ya da dosyayı yalnız o hesabın okuyabildiği bir klasöre koy; kendi kişisel hesabını otomasyon için kullanma.Etkisiz (idempotent) betik
İyi bir otomasyon betiğini iki kez üst üste çalıştırmak bir kez çalıştırmakla aynı sonucu vermelidir. Zaten taşınmış dosyayı tekrar taşımaya çalışmamak, var olan yedeği ezmemek, aynı e-postayı iki kez göndermemek (gönderildi bilgisini bir durum dosyasına yazmak) bu ilkenin parçalarıdır. Zamanlanmış görevler bazen iki kez tetiklenir ya da yarıda kesilir; idempotent betik bu durumları sorunsuz atlatır.
Kilit dosyası ve çakışan çalıştırmalar
Betik 15 dakikada bir çalışıyorsa ve bir çalıştırma 20 dakika sürerse ikisi aynı dosyalar üzerinde yarışır. Başta Path("calisiyor.lock").touch(exist_ok=False) ile kilit al, FileExistsError gelirse çık, finally içinde kilidi sil.
Atomik yazma ve geri dönüşüm kutusu
Raporu önce rapor.tmp'ye yazıp sonra os.replace ile asıl ada çevir; yarım dosya oluşmaz. Silme yerine send2trash paketiyle geri dönüşüm kutusuna gönder ya da "Silinecekler" klasörüne taşı ve 30 gün sonra temizle.
Gizli bilgiler ve yetki
Şifre, API anahtarı ve SMTP bilgileri koda değil ortam değişkenine ya da .env dosyasına (git'e eklenmeyen) yazılır. Otomasyon hesabına yalnızca gereken klasörlerde yetki ver; "her yere yazabilen bot" en tehlikeli kullanıcıdır.