# Geliştirme Günlüğü — Cari Takip Web

Bu dosya, web tabanlı Cari Takip Sistemi'nin geliştirilmesi sırasında yapılan tüm önemli işleri tarih sırasıyla kaydeder. İleride sistemi genişletecek kişiye (sen veya başkası) "ne yapıldı, neden yapıldı, nereye bakılmalı" sorularına hızlı cevap vermek için.

---

## 2026-08-15 — Kırık görsellerin GERÇEK sebebi: seeder dosyaları diske yazmıyordu

Görsellerin kırık görünmesini iki turda kod tarafında aradım ve iki gerçek hata
buldum (aşağıdaki kayıt). Ama sorun devam etti. `yonet.php` > **Görsel / Dosya Tanı**
raporu kesin cevabı verdi:

```
── belgeler.dosya_yolu ──
   #1001  ✓  [Proje]  belgeler/proje/419/OVkGeQ7k9byjMzsk2Pei.pdf     ← gerçek yükleme
   #1000  ✗  [Musteri] belgeler/musteri/401/TESTVERI-F2-FA34EA-200.pdf ← seeder
   ...
   TOPLAM 201 kayıt | son 25'in 1'i diskte VAR, 24'ü YOK
```

**Kök neden:** `TestVeriSeeder::belgeler()` DB'ye satır ekliyor ama **diske dosya
yazmıyordu**. Gerçek kullanıcı yüklemesi (#1001) sorunsuz çalışıyor; kırık görünen
her şey seeder'ın ürettiği hayalet kayıtlar. Yani kod doğruydu, **veri sahteydi**.

### Düzeltmeler
1. **Seeder artık gerçek dosya yazıyor** — `belgeDosyasiYaz()` eklendi.
   png/jpg/jpeg için **geçerli 8×8 PNG** (getimagesize ile doğrulandı), pdf için
   minimal geçerli PDF, csv/txt için metin. Önizleme dalları artık gerçekten test edilebilir.
2. **Seeder temizliği dosyaları da siliyor** — önce diskteki dosya, sonra DB satırı
   (ters sıra olsaydı yol bilgisi kaybolur, dosyalar storage'da yetim kalırdı).
3. **`yonet.php` > Eksik Dosyalı Belge Kayıtlarını Temizle** (YENİ) — mevcut bozuk
   kayıtları siler. İlk tıklama **kuru çalıştırma**: ne silineceğini listeler, hiçbir şey
   silmez; silmek için "Onaylıyorum" kutusu işaretlenmeli. **Dosyası yerinde olan
   belgelere dokunmaz.**
4. **`gorsel-tani` genişletildi** — `belgeler` tablosunu da kapsıyor (önceden hiç
   bakmıyordu) ve **arayüzde butonu yoktu**, eklendi. Ayrıca avatar/branding görselleri
   `public/uploads/` altında olduğu için yanlışlıkla ✗ işaretleniyordu; bunlar bilerek
   webroot'a yazılıyor (symlink'e bağlı kalmamak için), tanı artık doğru yerde arıyor.

### Sunucuda yapılacak
`yonet.php` > **Görsel / Dosya Tanı** → **Eksik Dosyalı Belge Kayıtlarını Temizle**
(önce kuru çalıştırma, sonra onaylayarak). Test verisi yeniden üretilirse dosyalar
bu kez diske de yazılır.

---

## 2026-08-15 — HATA: Dosya Yöneticisi görselleri 404 dönüyordu

**Belirti:** Dosya Yöneticisi'nde görseller kırık görünüyordu ("yollar yanlış" gibi).

**Kök neden:** Yol doğruydu — `Belge::getUrlAttribute()` düzgün bir route üretiyor
(`/belgeler/{id}/onizleme`). Sorun **yetki katmanındaydı**:

`BelgeController::preview()` ve `download()` şöyle başlıyordu:
```php
$entity = $belge->iliski;
$tip = $this->modelTipi(get_class($entity));   // <-- burada patlıyor
```
`modelTipi()` yalnızca modül modellerini (Cari, Personel, Teklif...) tanır ve
tanımadığında `default => abort(404)` der. Ama **Dosya Yöneticisi'nden yüklenen
dosyalar `iliski_type = Firma` ile kaydediliyor** (merkezi arşiv), Firma o
haritada YOK → her görsel **404**.

İkinci bir hata daha vardı: ilişkisi silinmiş dosyalarda `$belge->iliski` null
olur, `get_class(null)` **TypeError** fırlatır → 500. Dosya Yöneticisi'nin
"ilişkisiz" filtresi tam da bu dosyalar için var, yani bu yol gerçekten
tetikleniyordu.

**Çözüm:** `belgeErisimKontrol()` ortak metodu eklendi; iki uç da onu kullanıyor:
- **Modül belgesi** → eskisi gibi o modülün `goruntule` yetkisi
- **Firma'ya bağlı veya ilişkisiz belge** → `dosya_yonetici.goruntule` yetkisi
- Her iki yolda da firma izolasyonu korunur (`belgeler.firma_id` damgası + ilişki firması)

**Doğrulama (gerçek dosya, gerçek istek):**
| Senaryo | Önce | Sonra |
|---|---|---|
| Dosya Yöneticisi dosyası (Firma) | 404 | **200** |
| İlişkisi kopuk dosya | 500 (TypeError) | **200** |
| Modül dosyası (Cari) | 200 | **200** (değişmedi) |
| Yetkisiz kullanıcı | — | **403** |
| Başka firmanın dosyası | — | **403** |
Doğru firmadan istendiğinde gerçek dosya döndü (70 bayt PNG).

**Ek düzeltmeler (aynı ekran):**
- Kutu görünümünde görseller `object-cover` ile **kırpılıyordu** → `object-contain`.
  Geniş logolar ve dikey fotoğraflar artık tam görünüyor. Tablo görünümündeki
  küçük resim de aynı şekilde düzeltildi (+ ince çerçeve, şeffaf PNG kaybolmasın).
- Kutu görünümündeki özel toggle switch kaldırıldı; tablo görünümüyle **aynı
  `IndeterminateCheckbox`** kullanılıyor. Görselin üstünde okunsun diye beyaz
  zeminli kutunun içinde, etiketin tamamı tıklanabilir.
- `alt` metni dosya adını taşıyor, `loading="lazy"` eklendi.

---

## 2026-08-15 — HATA: iki controller'da `aktifFirmaId()` tanımsızdı

**Belirti:** Dosya Yöneticisi açılırken
`Call to undefined method DosyaYoneticiController::aktifFirmaId()`.

**Kök neden:** Bu proje aktif firmayı her controller'da ayrı ayrı tanımlanan
`private function aktifFirmaId(Request $request)` ile çözüyor (base `Controller`
sınıfında yok). `DosyaYoneticiController` (Parti 2) ve `SozlesmeImzaController`
(Parti 3) yazılırken metodu çağırdım ama **tanımlamayı unuttum**. `php -l`,
`tsc` ve `route:list` bunu yakalamaz — sadece o sayfa açıldığında patlar.
Her ikisini de yalnızca statik kontrolle geçirdiğim için kaçtı.

- `DosyaYoneticiController`: 4 çağrı → **/dosya-yonetici tamamen açılmıyordu**
- `SozlesmeImzaController`: 6 çağrı → imza daveti panel uçlarının hepsi 500 verirdi
  (liste, gönder, iptal, loglar, kayıttan gönder, özet)

**Çözüm:** Diğer controller'lardaki ile birebir aynı `private aktifFirmaId()`
metodu ikisine de eklendi. (Base sınıfa taşımak mümkün değil: mevcut
controller'larda metot `private`, ebeveynde `protected` olsaydı PHP görünürlük
daraltma hatası verirdi.)

**Tekrar kaçmasın diye:** yansıma tabanlı bir tarayıcı yazıldı — her controller'ın
`$this->xxx()` çağrılarını kalıtım + trait dahil gerçek metot listesiyle
karşılaştırıyor (grep'in yapamadığı bu). **52 controller tarandı, 0 tanımsız çağrı.**
Betik: `scratchpad/metot_tara.php` (kalıcı hâle getirilmek istenirse
`php artisan tinker --execute="require ..."` ile çalıştırılır).

**Doğrulama:** Dosya Yöneticisi 4 farklı filtreyle açıldı; ertelenmiş (defer)
prop'ların dördü de (`dosyalar`, `uzantilar`, `ozet`, `kullanicilar`) ayrı ayrı
çözüldü; `topluSil` çalıştı.

---

## 2026-08-15 — Modül Bilgi Butonları — 16 maddelik listenin 6. maddesi (SON MADDE)

Filtre butonu olan **44 ekranın tamamına**, filtrenin hemen yanına bir **"?"**
düğmesi kondu. Düğme o ekranın ne işe yaradığını, nasıl kullanıldığını ve
dikkat edilmesi gerekenleri anlatan bir pencere açar.

### Yapı
- `resources/js/lib/modulBilgi.ts` — **tüm içerik tek dosyada**. Her kayıt:
  `ozet` (bir paragraf), `bolumler[]` (başlık + madde listesi), `ipuclari[]`,
  `uyarilar[]`, `yetkiler[]`. Metinlerde `**kalın**` yazımı desteklenir.
- `resources/js/Components/ModulBilgi.tsx` — düğme + pencere. Kayıt yoksa
  **düğme hiç çizilmez** (yarım bir yardım penceresi açmaktansa hiç açmamak yeğdir).
- Raporlarda düğme `RaporAksiyonlar` bileşenine `bilgiKonu` prop'u ile eklendi —
  11 rapor ekranı için tek yerden.

### İçerik
30 konu yazıldı: cari · hareket · kasa · urun · stok_hareket · musteri ·
sozlesme · musteri_sozlesme · personel · personel_izin · proje · gorev ·
randevu · teklif · teklif_urun · teknik_servis · servis_yedek_parca ·
yenileme · yenileme_urun · yenileme_log · firma · kullanici · dosya_yonetici ·
aktivite_log · sistem_log + 11 rapor konusu.

Her listede tekrarlanan davranışlar (filtre paneli, toplu işlem, sayfalama,
sıralama, Pasife Al / Tamamen Sil ayrımı) `ORTAK_LISTE` sabitinde bir kez
yazılıp her kayda eklendi.

İçerik gerçek iş kurallarını anlatır, genel geçer laf değildir. Örnekler:
- **Cari ekstresi**: "Tür/tutar filtresi açıkken Bakiye sütunu yalnızca filtreye
  uyan satırları toplar."
- **Müşteri sözleşmesi**: "İmza atıldığı anda metin donar; farklı metin gerekiyorsa
  yeni sözleşme oluşturun."
- **Personel izni**: kıdem→gün tablosu (1–5 yıl **5 dahil** 14 gün ...), md. 56
  hafta tatili kuralı, yaşa bağlı istisnanın neden otomatik uygulanmadığı.
- **Ürün**: kritik/minimum stok eşiklerinin farkı.
- **Teklif ürünü**: "İlişkili tüm kayıtlarda güncellensin mi?" sorusunun ne yaptığı.

### Doğrulama
`npx tsc --noEmit` temiz · `npm run build` başarılı.
Filtre butonu olan ekran sayısı **44**, bilgi düğmesi olan ekran sayısı **44** —
eksik yok. Düğmenin filtrenin hemen ardında olduğu örnek ekranlarda doğrulandı.

### Deploy
Migration yok. `yonet.php` > **Önbelleği Temizle** yeterli.

---

## 2026-08-15 — Personel İzin Takibi — 16 maddelik listenin 4. maddesi

Personele yıllık izin hakkı, izin talebi ve onay akışı geldi.

### Veritabanı (`2026_08_15_000001_personel_izin_takibi`)
- `izin_turleri` — ad, renk, **`haktan_dusulur`**, `ucretli`, sıra, aktif.
  Her firmaya 7 varsayılan tür: Yıllık İzin (haktan düşer), Mazeret, Raporlu,
  Ücretsiz, Doğum, Evlilik, Ölüm.
- `personel_izin_haklari` — yıl bazında `hak_gun` + `devir_gun` + `elle_ayarlandi`.
  `(personel_id, yil)` unique.
- `personel_izinleri` — tarih aralığı, gün sayısı, yıl, durum
  (bekliyor / onaylandi / reddedildi / iptal), red nedeni, onaylayan, aktif.
  Tür silinirse `nullOnDelete` + `izin_turu_ad` anlık kopya (geçmiş kayıt bozulmaz).

### İş kuralları
- **Yıllık hak, 4857 md. 53'e göre kıdemden hesaplanır:** 1 yıldan az → 0,
  1–5 yıl (5 DAHİL) → 14, 5'ten fazla 15'ten az → 20, 15 ve üzeri → 26 gün.
  Yaşa bağlı istisna (18 altı / 50 üstü ≥20 gün) doğum tarihi tutulmadığı için
  otomatik uygulanmaz — hak günü elle düzeltilebilir, `elle_ayarlandi` işaretlenir
  ve toplu hesaplama o kaydın üzerine YAZMAZ.
- **Gün sayısı md. 56'ya göre:** varsayılan Cumartesi + Pazar hariç.
  "Cumartesi iş günü" ve "takvim günü (hafta sonu dahil)" seçenekleri var;
  gün sayısı elle de girilebilir.
- **Yalnızca `haktan_dusulur` türler bakiyeden düşer.** Rapor/doğum izni kayda
  geçer ama yıllık hakkı azaltmaz. Bekleyen talepler ayrı "rezerve" gösterilir.
- **Çakışma engeli:** aynı personelde tarihleri kesişen bekleyen/onaylı izin varsa
  kayıt reddedilir (hangi izinle çakıştığı mesajda yazar).
- **Bakiye kontrolü onay anında:** kalan yetmiyorsa onaylanamaz, ne kadar
  eksik olduğu söylenir.
- **Red nedeni zorunlu** — reddetme ayrı modalda (sistem kuralı: native prompt yasak).

### Ekranlar
- `PersonelIzin/Index` — tüm izinler; özet kartlar (onay bekleyen / bugün izinde /
  onaylı / toplam gün), filtre çekmecesi (personel · tür · durum · yıl · tarih
  aralığı · bugün izinde · pasif), toplu silme, sayfalama, satırdan onayla/reddet/iptal.
  "Hakları Hesapla" butonu yılın haklarını kıdemden toplu üretir.
- `PersonelIzin/PersonelKarti` — tek personel: kıdem, 5 bakiye kartı
  (hak / devir / toplam / kullanılan / bekleyen), yıl seçici, hak düzenleme formu,
  o yılın izin geçmişi.
- `PersonelIzin/_FormModal` — tarih seçilince gün sayısı sunucudan canlı hesaplanır,
  seçili personelin bakiyesi anlık gösterilir, talep bakiyeyi aşıyorsa uyarır.
- `PersonelIzin/_RedModal` — red nedeni zorunlu.

### Zorunlu entegrasyonlar
`personel.izin` + `personel.izin_onay` yetkileri · Moduller.php sidebar ("İzinler",
`route_prefixes`'e `izinler`) · Tanımlamalar > **İzin Türleri** (`izin-turu`) ·
AktiviteLog modülü `personelizin` (personel loglarıyla karışmasın diye ayrı kod) ·
Personel listesine izin ikonu · **Dashboard widget** (onay bekleyen + bugün izinde
olanların listesi, personel paneliyle birlikte çizilir).

### Doğrulama — gerçek veriyle çalıştırıldı
Kıdem→hak eşlemesi 8 senaryoda doğrulandı (5 ve 15 yıl sınırları dahil).
Gün hesabı 5 senaryoda doğru (Pzt-Cum=5, hafta sonu dahil=7, cumartesi dahil=6,
ters aralık=0). Yıllık izin bakiyeden düştü, raporlu düşmedi. Çakışma engellendi.
Bekleyen→onay geçişinde bakiye 20→15→10 doğru ilerledi. Red nedeni zorunluluğu ve
bakiye aşımı engeli çalıştı. Her iki ekran Inertia props'larıyla render oldu,
`gunHesapla` ve `ozet` API'leri doğru JSON döndü, 6 aktivite logu yazıldı.
Tüm test verisi rollback edildi.

### Deploy
`yonet.php` > **Veritabanını Güncelle** (`2026_08_15_000001`) → **Önbelleği Temizle**.
Ardından ilgili kullanıcılara `personel.izin` / `personel.izin_onay` yetkisi verilmeli.
İlk kurulumda İzinler ekranından **"Hakları Hesapla"** çalıştırılmalı.

---

## 2026-08-15 — Sözleşme Akışı Yeniden Tasarlandı (kullanıcı düzeltmesi)

Bir önceki gün kurulan "imza daveti" akışı kullanıcının istediği şey değildi. Eski akışta
davet ayrı bir şablon seçimiyle başlıyor, kendi snapshot'ını üretiyor ve imzalanınca
**yeni** bir sözleşme kaydı açıyordu. Doğrusu: sözleşme önce kaydedilir, sonra
**o kayıt** imza için gönderilir.

### Yeni akış
1. **Sözleşme Ekle** — şablon seçilir, metin bu müşteri için doldurulur.
2. **Kaydet** — imza ve ad soyad doluysa `imzalandi`, boşsa `taslak` olarak kaydeder.
   İmza artık ZORUNLU DEĞİL. (Tek başına imza / tek başına ad girilirse uyarır.)
3. **Metni Düzenle** — taslak aşamasında metin **bu müşteriye özel** değiştirilebilir
   (fiyat, madde, tarih). Şablon ve diğer müşterilerin sözleşmeleri etkilenmez.
   Hem ekleme modalında hem görüntüleyicide mevcut.
4. **İmza için gönder** — listedeki bağlantı ikonu; **bu kaydın metnini** gönderir.
   Müşteri imzalayınca AYNI kayıt imzalanmış olur, kopya kayıt oluşmaz.
5. **Müşteriye İlet** — yalnızca imzalı kayıtta çıkar (onaylı PDF e-postası).

### Snapshot kuralı korundu, sadece inceltildi
`MusteriSozlesme::booted()` artık `getOriginal('durum')` bakıyor:
imzalanmış bir kaydın `DONMUS_ALANLAR`'ına dokunmak hâlâ istisna fırlatır, ama
taslak → imzalandı geçişi (aynı update içinde) serbest. Yani **imzalı sözleşme
asla bozulmaz**, imza öncesi düzenleme serbesttir.

### Değişiklikler
- `2026_08_15_000002_musteri_sozlesme_imzasiz` — `imza_ad_soyad` + `imza` nullable,
  `durum` kolonu (`taslak` / `imzalandi`), mevcut imzalı kayıtlar geriye dönük işaretlendi.
- `MusteriSozlesme` — `DURUM_*` sabitleri, `imzalandiMi()`, `duzenlenebilirMi()`.
- `MusteriSozlesmeController::store()` — imza opsiyonel, düzenlenmiş metni kabul eder,
  bilgilendirme maili yalnızca imzalıda gider.
- `MusteriSozlesmeController::guncelle()` (YENİ) — taslak metnini müşteriye özel günceller;
  imzalıda 422. Aynı istekte imza gelirse imzalanmış hâle geçirir.
- `SozlesmeImzaController::kayittanGonder()` (YENİ) — mevcut kayıttan davet üretir;
  aynı kayıt için bekleyen davet varsa yenisini AÇMAZ, metnini tazeleyip tekrar gönderir.
- `SozlesmeImzaController::imzala()` — `musteri_sozlesme_id` doluysa o kaydı imzalar,
  yalnızca bağlantısız/eski davetlerde yeni kayıt oluşturur.
- Liste — **Durum** kolonu (İmzalandı / İmza Bekliyor rozeti), filtre panelinde durum
  seçimi, taslakta bağlantı ikonu · imzalıda gönder ikonu.
- `_ImzaGonderModal` (YENİ) — kayıt bazlı gönderim + kopyalanabilir bağlantı.
- `_GosterModal` — durum rozeti, taslakta "Metni Düzenle" + "Metni Kaydet".
- `_DavetModal` — artık yalnızca **gönderim geçmişi** (şablon seçen "Yeni Davet" formu kaldırıldı).

### Doğrulama
`php -l` temiz · `npx tsc --noEmit` temiz · `npm run build` başarılı · 23 sözleşme rotası ayakta.
**Lokal DB'de `musteri_sozlesmeleri` tablosu olmadığı için akış çalıştırılarak test EDİLEMEDİ**
(migration zinciri sqlite'ta kırık — bkz. aşağıdaki not). Sunucuda ilk denemede
taslak kaydetme → metin düzenleme → imza gönderme → müşteri imzalama sırası kontrol edilmeli.

### Deploy
`yonet.php` > **Veritabanını Güncelle** (`2026_08_15_000002`) → **Önbelleği Temizle**.

---

## 2026-08-15 — Raporlama Overhaul — 16 maddelik listenin 1, 2 ve 3. maddeleri

**11 rapor ekranının tamamı** aynı iskelete geçirildi: zorunlu sayfalama, filtre çekmecesi,
gerçek grafikler (recharts) ve baskıya hazır çıktı.

### Ortak bileşenler (`resources/js/Components/Rapor/`)
- `RaporKart` — özet kartı; her raporda ayrı ayrı kopyalanmış `KartTipi`/`Kart` bileşenlerinin yerini aldı.
- `RaporGrafik` — `SutunGrafik` / `PastaGrafik` / `CizgiGrafik` / `OranListesi` + `GrafikKutu`.
  Türkçe sayı biçimi, boş-veri durumu, tema paleti tek yerde. `recharts` zaten bağımlılıktaydı,
  ilk kez gerçekten kullanılıyor (önceden yalnızca CSS çubuk vardı).
- `RaporAksiyonlar` — filtre + Yazdır + Excel + PDF butonları; **tablet/mobilde yalnızca ikon**
  (sistemdeki genel kural). `raporSorgu()` ekrandaki filtreyi indirme bağlantısına birebir taşır.
- `RaporGovde` — `.rapor-yazdir` sarmalayıcısı + yalnızca kâğıtta görünen başlık (firma, rapor adı, dönem).

### Baskıya hazır çıktı (`resources/css/app.css`)
`@media print` bloğu eklendi: yazdırırken sidebar/üst bar/filtre çekmecesi/sayfalama çıkmaz,
tablo başlığı her sayfada tekrarlanır, kartlar ve grafikler sayfa ortasından bölünmez,
`liste-tablosu`nun `white-space: nowrap` kuralı baskıda gevşetilir. Yardımcı sınıflar:
`.rapor-yazdir` · `.yazdirma-gizle` · `.yazdirma-butun` · `.yalniz-yazdir`.

### Sayfalama — artık İSTİSNASIZ hepsinde
Eksik olan 4 rapor tamamlandı: **Cari Ekstresi, Kasa Raporu, Mali Özet, Personel Bordrosu**.
Yürüyen bakiye (cari/kasa) tüm set üzerinden hesaplandığı için SQL LIMIT kullanılamaz —
`diziPaginate()` yardımcısı hesap bittikten sonra diziyi dilimler. Personel bordrosunda mesai ve
ödeme listeleri **ayrı** sayfalanır (`mesai_sayfa` / `odeme_sayfa`), biri diğerini kaydırmaz.

### Gelişmiş filtreler (her rapora özel, FilterDrawer ile)
| Rapor | Filtreler |
|---|---|
| Cari Ekstresi | tür · yön · tutar aralığı · belge no · açıklama |
| Kasa Raporu | tür · yön · cari · tutar aralığı · açıklama |
| Mali Özet | tür · yön · kasa · cari · tutar aralığı · açıklama |
| Personel Bordrosu | ödeme türü · kasa · yalnızca fazla mesai · açıklama |
| Proje | statü · tip · müşteri · yetkili · durum (aktif/pasif/tamamlandı/geciken) · başlık |
| Görev | durum · öncelik · atanan · atayan · yalnızca geciken · başlık |
| Randevu | statü · tip · müşteri · personel · zaman (bugün/gelecek/geçmiş) · başlık |
| Teklif | statü · müşteri · atanan · durum (tamamlandı/bekleyen/süresi geçmiş) · başlık+kod |
| Müşteri | kategori · şehir · sektör · durum · unvan |
| Stok Envanteri | kategori · stok durumu · sıfır stoğu gizle · min. stok değeri · ad/kod/barkod |
| Stok Hareket Özeti | kategori · ürün · hareket tipi · ad/kod |

Filtreler **hem özet/grafik hem detay sorgusuna** aynı closure ile uygulanır — kartlardaki sayı ile
tablodaki satır sayısı ayrışmaz. Cari/kasa raporlarında filtre açıkken bakiye kolonu yalnızca
filtreye uyan satırları topladığı için ekranda **sarı uyarı** çıkar (`filtre_aktif` bayrağı).

### Yeni analiz + grafikler
Her rapora aylık zaman serisi (`aylikSeri()` — "Oca 26" biçimi, sürücüye göre `strftime`/`DATE_FORMAT`)
ve dağılım pastaları (`dagilimSeri()` — ilk 10 + "Diğer") eklendi. Mali Özet'e ayrıca gelir/gider
kalem pastaları ve **sayfalı hareket detayı** tablosu; Stok Envanteri'ne stok sağlığı pastası;
Teklif'e aylık adet+tutar sütunu.

### Yeni çıktı uçları
`mali-ozet/excel` · `personel-bordrosu/excel` · `stok/hareket-ozet/pdf` · `stok/hareket-ozet/excel`
(+ yeni blade `raporlar/stok-hareket-ozet.blade.php`).

### Yol boyunca çıkan hatalar (hepsi onarıldı)
1. **Teklif raporunda N+1** — her satır için ayrı `SUM(miktar*birim_fiyat)` sorgusu çalışıyordu;
   `withSum('satirlar as satir_toplam', ...)` ile tek sorguya indi.
2. **Stok durum kodları tutmuyordu** — `Urun::getStokDurumuAttribute()` `kritik/dusuk/yeterli`
   dönerken ekrandaki renk haritası `yeterli/az/kritik/bitti` bekliyordu: "düşük" stoktaki ürünler
   rozetsiz basılıyordu. Üç koda da hizalandı.
3. **PDF layout'unda tanımsız değişken** — `raporlar/_layout.blade.php` her raporda
   `$baslangic`/`$bitis` bekliyordu; stok envanteri gibi anlık raporlarda uyarı üretiyordu.
   `@isset` ile korundu, tarih aralığı yoksa "Rapor Tarihi" yazıyor.
4. **`admin/excel-import` rotaları** var olmayan `ExcelImportController`'ı çağırıyordu
   (500 + `route:list` çöküyordu) — açıklamayla yorum satırına alındı.
5. **Migration'lar MySQL'e bağımlıydı** — `roller_kaldir` ve `gorev_atananlar_coklu`
   `information_schema` sorguluyor, `users_kullanici_adi_kaldir` `ALTER TABLE ... DROP INDEX`
   kullanıyordu; sqlite'ta patlıyorlardı. Üçü de sürücü kontrolüyle taşınabilir hale getirildi.

### Doğrulama
`npx tsc --noEmit` temiz · `npm run build` başarılı · `route:list` 36 rapor rotası.
Cari/kasa/mali/bordro/müşteri/stok veri metotları filtreli + filtresiz + boş seçim hâlleriyle
çalıştırıldı; geçici veriyle (40 hareket, 3 ürün, 18 stok hareketi) sayfalama sınırı, yürüyen
bakiye tutarlılığı, `son = devir + giriş − çıkış` eşitliği ve filtre etkisi doğrulandı, veriler
rollback edildi. Dört muhasebe/personel PDF'i + iki stok PDF'i üretildi.

> **Not:** Proje/Görev/Randevu/Teklif raporları lokal sqlite'ta **çalıştırılarak** doğrulanamadı —
> o modüllerin tabloları lokal veritabanında yok (migration zinciri sqlite'ta hâlâ
> `users.rol_id` FK'sinde kırılıyor; canlı MySQL'de sorun değil). Bu dördü için yalnızca
> statik doğrulama (php -l, tsc, build, route:list) yapıldı.

### Deploy
Migration yok. `yonet.php` > **Önbelleği Temizle** yeterli (blade cache + route cache).

---

## 2026-08-14 — Sözleşme İmza Daveti (uzaktan imzalatma) — 16 maddelik listenin 5. maddesi

Müşteri sözleşmesi artık sadece panel başında imzalanmıyor: müşteriye **bağlantı** gönderilip
giriş yapmadan imzalatılabiliyor. İmza hukuki sonuç doğurduğu için **her adım loglanıyor**.

### Veritabanı
- `2026_08_14_000001_sozlesme_imza_daveti` — iki tablo:
  - `sozlesme_imza_davetleri` — token (48 hex), **snapshot içerik** (davet anında dondurulur),
    durum (bekliyor / goruntulendi / tamamlandi / iptal / suresi_doldu), gönderilen mail + ek mailler,
    gönderim / ilk görüntüleme / tamamlanma tarihleri, `son_gecerlilik`, `musteri_sozlesme_id`.
  - `sozlesme_imza_loglari` — denetim kaydı: tip, başlık, açıklama, **IP + tarayıcı**, kullanıcı, `detay` JSON.
- `2026_08_14_000002_sozlesme_imza_mail_sablonu` — `sozlesmeler` tablosuna `imza_mail_konu` + `imza_mail_govde`.

### Snapshot kuralı (kritik)
Metin **davet anında** dondurulur. Şablon sonradan değişse bile müşteri gördüğü metni imzalar;
imza tamamlanınca aynı metin `musteri_sozlesmeleri` kalıcı kaydına geçer. Şablon silinse dahi
imzalı kayıt bozulmaz (`nullOnDelete`).

### Backend
- `SozlesmeImzaDaveti` — `imzaUrl()`, `imzalanabilirMi()` (tamamlanmış/iptal/süresi dolmuş kabul etmez).
- `SozlesmeImzaLog::yaz()` — tek satır log kısayolu; IP + user agent + kullanıcıyı otomatik yakalar,
  log yazılamazsa asıl işlemi bozmaz. 8 tip: `olusturuldu`, `mail_gonderildi`, `mail_hatasi`,
  `link_acildi`, `imzalandi`, `iptal_edildi`, `suresi_doldu`, `bilgilendirme_gonderildi`.
- `SozlesmeImzaController` — panel: `index` / `store` / `gonder` / `iptal` / `loglar`;
  public: `imzaSayfasi` / `imzala`. `davetMailIcerik()` **`{imza_linki}` garantisi** verir —
  şablondan silinmişse gövdenin sonuna otomatik eklenir (linksiz davet işe yaramaz).
- Yer tutucular: `{imza_linki}` + `{son_gecerlilik}` GRUP_SOZLESME'ye eklendi.

### Frontend
- `Public/SozlesmeImza.tsx` — giriş gerektirmeyen imza sayfası (snapshot metin + SignaturePad + onay).
- `Sozlesme/_MailSablonModal` — **%40 İmza Sonrası Bilgilendirme / %40 İmza Daveti / %20 Yer Doldurucular**;
  iki editör + iki konu alanı, 4 yönlü `hedefAlan` (token hangi alana gidecek panelde yazılı), mobilde 3 sekme.
- `MusteriSozlesme/_DavetModal` — tek modalda iki görünüm:
  - **Liste** — durum rozeti, bağlantı kopyala, (tekrar) gönder, iptal (styled onay penceresi),
    oluşturma / gönderim / ilk görüntüleme / son geçerlilik tarihleri;
    **History ikonu → davet başına log zaman çizelgesi** (ikonlu, IP + tarayıcı + kullanıcı ile).
  - **Form** — AsyncSelect şablon, geçerlilik günü, "e-posta ile gönder" toggle, ek mail listesi.
    Kayıttan sonra bağlantı kopyalanabilir ekranda gösterilir (mail kapalıyken de link üretilir).
- `MusteriSozlesme/Index` — filtre butonunun sağına `Link2` ikonu (imza davetleri).

### Yol boyunca çıkan hata
`routes/web.php` içindeki `admin/excel-import` bloğu **var olmayan** `ExcelImportController`
sınıfını çağırıyordu: sayfa 500 veriyor, `php artisan route:list` ReflectionException ile
patlıyordu. Blok açıklamayla yorum satırına alındı (ekran dosyası `Pages/Admin/ExcelImport.tsx` duruyor).

### Bilinen lokal sorun (sunucuyu etkilemez)
`2026_05_12_000009_users_kullanici_adi_kaldir` lokal **sqlite**'ta patlıyor
("no such column: kullanici_adi" — sqlite index/drop-column kısıtı). Canlı MySQL'de sorun değil,
migration çoktan uygulanmış. Yeni iki migration tek tek çalıştırılarak doğrulandı.

### Deploy
`yonet.php` > **Veritabanını Güncelle** → iki yeni migration. Ardından **Önbelleği Temizle**.
Yetkiler: mevcut `musteri_sozlesme.goruntule` (log okuma) ve `musteri_sozlesme.ekle`
(davet oluştur / gönder / iptal) kullanılıyor, yeni yetki eklenmedi.

---

## 2026-06-16 — Teknik Servis Modülü (İterasyon 1: Veritabanı + Modeller + Manifest)

Yeni modül `teknik_servis` eklendi. İterasyon 1: backend iskelet (controller/route hariç).

**Migration'lar** (`2026_06_18_010001 .. 010010`):
- `servis_durumlari` — TeklifStatu pattern, mail_konu + mail_govde, tamamlandi_mi
- `servis_turleri` — basit kategori-benzeri (firma_id, ad, renk, sıra)
- `servis_hizmet_turleri` — aynı
- `servis_yedek_parcalari` — TeklifUrun pattern (katalog: ad, fiyat, görsel, açıklama)
- `servisler` — ana tablo: servis_no (otomatik SRV-YYYY-0001), başlık, kayıt/işlem tarihi, durum/tür/hizmet, personel (User), müşteri, teslim eden/alan, marka/model/seri, fatura_var, müşteri_beyani + personel_notlari (longText)
- `servis_satirlari` — TeklifSatir pattern (yedek_parca_id + parca_adi + miktar/fiyat)
- `servis_yorumlari` — Proje yorumları pattern
- `servis_ek_alanlar` — başlık + içerik artırılabilir, sıralı (Firma banka bilgileri pattern)
- `servis_seed` — kullanıcı listesinden 15 durum + 5 tür + 12 hizmet türü her firmaya işlenir
- `teknik_servis_modulu_firmalara_ata` — `firma_modulleri` tablosuna kayıt ekle

**Modeller** (`app/Models/`):
- `ServisDurum`, `ServisTuru`, `ServisHizmetTuru`, `ServisYedekParca`
- `Servis` — `booted::creating` ile servis_no otomatik atama; belgeler MorphMany
- `ServisSatir`, `ServisYorum`, `ServisEkAlan`

**Manifest + Yetki**:
- `App\Support\Moduller::liste()` → `teknik_servis` eklendi, route_prefixes: `servisler` + `servis-yedek-parcalari`
- `App\Support\Permissions` → `aciklamalar()` ve `gruplar()` (9 yetki: goruntule, ekle, duzenle, sil, toplu_sil, tum_goster, mail_gonder, parca, rapor)

**İterasyon 2'de**: Controllers + Routes + AsyncSelect endpoint'leri (`/api/ara/servisler`, `/api/ara/servis-yedek-parcalari`)
**İterasyon 3'te**: Frontend (Takvim/Board/Tablo/FormModal/FiltreDrawer/BildirimModal/Yedek Parça sayfaları)
**İterasyon 4'te**: Tanımlamalar UI + Dashboard widget + Sidebar
**İterasyon 5'te**: Test + build + dev_log son

---

## 2026-06-16 — Teknik Servis Modülü (İterasyon 2: Controllers + Routes + AsyncSelect)

**Yeni controller'lar:**
- `app/Http/Controllers/TeknikServisController.php` — TeklifController + RandevuController birleşimi. Yöntemler: `takvim/board/tablo`, `takvimVeri` (JSON), `formJson`, `store/update/destroy/topluSil`, `durumDegistir` (board drag), `tarihDegistir` (takvim drag), `sirala`, `alanGuncelle`, `yorumEkle`, `bildir` (mail attach destekli).
- `app/Http/Controllers/ServisYedekParcaController.php` — TeklifUrunController pattern. CRUD + görsel yükleme (`storage/servis-yedek-parcalari/{firmaId}/`).

**Tanımlamalar generic sisteme entegre:**
- `KategoriController::TIPLER` → 3 yeni: `servis-durum` (mail_sablon=true), `servis-tur`, `servis-hizmet-turu` — hepsi `modul=teknik_servis`.
- Yeni endpoint: `servisDurumMailKaydet()`.

**Arama (AsyncSelect):**
- `AraController::servisler` ve `AraController::servisYedekParcalari` (yeni)
- `AraController::cozumle` — `tip=servis-yedek-parca` ve `tip=servis` desteği eklendi.

**Belge / Dosya:**
- `BelgeController` — `tip=servis` eşleştirmesi. `yetkiOnEki()` helper: `servis → teknik_servis`.

**Routes (`routes/web.php`):**
- `Route::middleware('modul:teknik_servis')->group()` — 17 route.
- `/api/ara/servisler`, `/api/ara/servis-yedek-parcalari`.
- `/servis-durum/{durum}/mail` mail şablonu.
- Belge regex'lerine `servis` eklendi.

**İterasyon 3'te**: Frontend sayfaları.

---

## 2026-05-07 — [Faz 1 / İterasyon 1] Web Tabanlı Yeniden Yazım — Çekirdek Altyapı

**Yapılanlar:**

### 1. Stack ve Mimari
- **Önceki sistem** (`cari-excel-sistemi/`, Excel + VBA + Access) görsel olarak yetersiz bulundu. Kullanıcı "her yerden erişilebilir, görsel modern, geliştirilebilir" bir sistem istedi.
- **Yeni stack:** Laravel 11 (PHP 8.3) + Inertia.js + React 18 + TypeScript + Tailwind CSS 3 + SQLite (dev) → MySQL (prod).
- Auth: Laravel Breeze + role/permission sistemi.
- Hosting hedef: cPanel (Türkiye'de yaygın, KVKK uyumlu).

### 2. Geliştirme Ortamı (Laragon Full)
- **Laragon Full** kuruldu (`C:\laragon`): PHP 8.3 + MySQL 8.4 + Composer + Node 22 + Git hepsi tek paket.
- Laragon menü → Tools → Path → "Add Laragon to Windows PATH" ile sistem PATH'e eklendi.
- PHP `zip` extension (`php.ini` içinde `;extension=zip` → `extension=zip`) açıldı; composer paketleri zip ile indirebilsin.
- composer process-timeout 0 (sınırsız) ayarlandı.

### 3. Laravel + Breeze Kurulumu
- `composer create-project laravel/laravel cari-takip-web` → Laravel 11 iskelet
- `composer require laravel/breeze --dev` → Breeze auth paketi
- `php artisan breeze:install react --typescript` → Inertia + React + TypeScript scaffolding
- `npm install --legacy-peer-deps` → 237 paket (Vite 8 / @vitejs/plugin-react peer çakışması nedeniyle legacy mode)
- `npm install lucide-react axios --legacy-peer-deps` → ikonlar + HTTP client
- `php artisan key:generate` → APP_KEY (eksik olunca login sayfası 500 dönüyordu)

### 4. Veritabanı
- `.env` SQLite'a göre yapılandırıldı (DB_CONNECTION=sqlite, DB_DATABASE=database/database.sqlite).
- `database/database.sqlite` boş dosya oluşturuldu.
- **Tek büyük migration** yazıldı (`2026_05_07_000001_create_cari_takip_schema.php`) — 19 tablo:
  - roller, yetkiler, user_firma (M:N pivot)
  - users (alter: rol_id, kullanici_adi, aktif, son_giris)
  - firmalar
  - cari_kategorileri, cariler
  - kasalar
  - hareket_turleri, hareketler
  - personeller, mesai_kayitlari
  - odeme_turleri, personel_odemeleri
  - belgeler (polymorphic), doviz_kurlari
  - ozel_alanlar, ozel_alan_degerleri (kullanıcı tanımlı kolonlar)
  - ayarlar, sistem_loglari
- Tablo isimleri Türkçe (firmalar, cariler, hareketler), kolon isimleri snake_case Türkçe (firma_id, cari_kodu, vergi_no).

### 5. Eloquent Models
- `User`, `Rol`, `Yetki`, `Firma`, `Cari`, `CariKategori`, `Kasa`, `HareketTuru`, `Hareket`, `Personel`, `MesaiKaydi`, `OdemeTuru`, `PersonelOdeme`.
- Relations: HasMany, BelongsTo, BelongsToMany.
- Business logic methods: `Cari::bakiyeTL()`, `Kasa::bakiye()`, `Personel::donemKazanci()`, `Personel::netBorc()`, `User::yetkiKontrol()`.

### 6. Seeder (`DatabaseSeeder.php`)
- Default roller: Admin, Muhasebe, Görüntüleyici (yetki matrisi ile).
- Default admin kullanıcı: `admin@cari.local` / `admin123` (kullanıcı adı: admin).
- Örnek firma: "Örnek Şirket A.Ş.".
- Default cari kategorileri (Müşteri/Tedarikçi/Personel/Banka/Diğer).
- Default hareket türleri (Tahsilat/Ödeme/Fatura Kesildi/Geldi/Çek Tahsilatı/Masraf/Personel Ödemesi).
- Default ödeme türleri (Maaş/Avans/Mesai/Prim/SGK/İkramiye).

### 7. Modern UI — AppLayout.tsx
- Sol panel: koyu lacivert (`bg-slate-900`), 9 menü maddesi, lucide-react ikonlar.
- Aktif menü: `bg-blue-600 text-white`.
- Üst bar: beyaz arka plan, sticky header, kullanıcı profili dropdown'u.
- Sağ alt köşede oturum kullanıcı avatarı + isim/email.
- Mobile responsive: sidebar sürgülü, hamburger menu.
- Flash message'lar yeşil bantta gösterilir.

### 8. Dashboard.tsx — Modern Kart Widget'lar
- **4 ana kart:** Toplam Alacak / Toplam Borç / Bu Ay Tahsilat / Bu Ay Ödeme — her biri farklı renk + lucide ikon.
- **Kasa Bakiyeleri tablosu** (responsive).
- **Son 10 Hareket listesi.**
- **3 Hızlı Erişim kartı:** + Yeni Cari / + Yeni Hareket / + Yeni Kasa (dashed border, hover effect).

### 9. Cari Modülü Vertical Slice (TAM)
- `CariController.php`: index/create/store/edit/update/destroy. Yetki kontrolü + firma izolasyonu (kullanıcı sadece kendi firmasının cari'lerini görür).
- `Cari/Index.tsx`: arama + kategori filtre + 7 kolonlu tablo + sayfalama + Düzenle/Sil ikon butonları + boş hal mesajı.
- `Cari/Form.tsx`: 2 panel (Genel + Vergi/İletişim) + bakiye özeti panel (sadece düzenleme modu).
- Otomatik cari kodu üretimi (`120.001`, `120.002` formatında).
- Cari silme: hareket varsa pasif yapılır (soft), yoksa silinir (hard).

### 10. Format Helpers (`resources/js/lib/format.ts`)
- `paraFormat(value, currency)`: Intl.NumberFormat ile TR locale.
- `tarihFormat(d)`, `tarihFormatUzun(d)`: TR locale tarih.

**Eklenen/Değişen Dosyalar:**
- `app/Http/Controllers/CariController.php`, `DashboardController.php`
- `app/Models/User.php` (override), `Rol.php`, `Yetki.php`, `Firma.php`, `CariKategori.php`, `Cari.php`, `Kasa.php`, `HareketTuru.php`, `Hareket.php`, `Personel.php`, `MesaiKaydi.php`, `OdemeTuru.php`, `PersonelOdeme.php`
- `database/migrations/2026_05_07_000001_create_cari_takip_schema.php`
- `database/seeders/DatabaseSeeder.php`
- `resources/js/Layouts/AppLayout.tsx`
- `resources/js/Pages/Dashboard.tsx`
- `resources/js/Pages/Cari/Index.tsx`, `Form.tsx`
- `resources/js/lib/format.ts`
- `resources/js/bootstrap.ts` (eksikti, axios konfigürasyonu)
- `routes/web.php`
- `.env` (SQLite, APP_NAME)

**Bilinen Kısıtlar / TODO (Sıradaki İterasyonlar):**
- **Faz 1 / Iter-2:** Kasa modülü (Controller + Index + Form), Hareket modülü.
- **Faz 1 / Iter-3:** Personel modülü (liste + detay + mesai + ödeme — kasa ile entegre).
- **Faz 1 / Iter-4:** Firma yönetimi, Kullanıcı yönetimi, Kategori/Tür yönetimi.
- **Faz 2:** Çoklu firma seçim/geçiş (üst bar dropdown), tema toggle (koyu/açık), profil ayarları genişletilmiş.
- **Faz 3:** TCMB API entegrasyonu (çoklu para birimi otomatik kur), belge ekleme, kullanıcı tanımlı özel alanlar.
- **Faz 4:** Recharts ile gerçek dashboard grafikleri, raporlar (cari ekstresi, kasa raporu, maaş bordrosu PDF), Excel export.
- **Faz 5:** Production deployment — cPanel ortamına taşıma talimatı (.env'i prod'a uygunlaştırma, MySQL'e geçiş migration, public_html ayarları).

**Test Notu:**
- Server: `php artisan serve` — http://127.0.0.1:8000
- Login: admin@cari.local / admin123
- Test akışı: Login → Dashboard (kart widget'lar görünür) → soldan "Cariler" → Yeni Cari ekle → liste güncellenir → düzenle/sil → Dashboard'da değişiklikler yansır.
- Tüm Türkçe karakterler (Ç, Ğ, İ, Ö, Ş, Ü) doğru render edilir.

---

---

## 2026-05-07 — [Faz 1 / İterasyon 2] Kasa + Hareket Modülleri

**Yapılanlar:**

### Git
- Initial commit yapıldı (admin@cari.local — Elitnet, "Faz 1 / Iter-1" mesajı).
- Local git config: user.email=elitseo59@gmail.com, user.name=Elitnet.

### Kasa Modülü (Tam Vertical Slice)
- `KasaController.php` — index/create/store/edit/update/destroy + firma izolasyonu + soft/hard delete (hareket varsa pasif).
- `Pages/Kasa/Index.tsx` — **kart tabanlı görünüm** (tablo değil, daha modern):
  - Üstte 3 kart: TRY/USD/EUR toplam bakiyeler.
  - Her kasa için kart: tip ikonu (Wallet/Landmark/CreditCard), ad, tip, para birimi, **güncel bakiye** (büyük, renkli), açılış bakiyesi, açıklama, Düzenle/Sil butonları.
  - Boş hal: dashed border içinde call-to-action.
- `Pages/Kasa/Form.tsx` — Ad/Tip/Para Birimi/Açılış Bakiyesi/Açıklama + güncel bakiye özeti (sadece düzenleme).

### Hareket Modülü (Tam Vertical Slice)
- `HareketController.php` — kasa/cari/tarih filtresi, `tutar_tl` otomatik hesaplama (kur × tutar).
- `Pages/Hareket/Index.tsx`:
  - **5 kolonlu filtre bar** (tarih başlangıç/bitiş, cari, kasa) — default bu ay.
  - **3 kart özet:** kayıt sayısı, net TL (alacak − borç), tarih aralığı.
  - 8 kolonlu tablo: Tarih, Tür (renkli pill: Alacak yeşil, Borç kırmızı), Cari, Kasa, Belge No, Tutar, TL Karşılığı (ön işaretli), İşlem ikonları.
  - Sayfalama (50 kayıt/sayfa).
- `Pages/Hareket/Form.tsx`:
  - Tarih + Tür + Kasa + Cari (opsiyonel) + Tutar + Para Birimi + Kur + Belge No + Açıklama.
  - **Canlı TL hesabı**: tutar × kur reaktif olarak alt panelde gösterilir.
  - TRY seçilince kur otomatik 1 ve disabled.
  - Tür yön'üne göre özet panelin rengi değişir (Alacak yeşil, Borç kırmızı).

### Routes
- `routes/web.php` üç resource controller: cariler, kasalar, hareketler — auth middleware altında.

### Build
- `npm run build` başarılı — yeni Kasa/Hareket page'leri için ek bundle'lar oluştu.
- `php artisan serve` ayakta, http://127.0.0.1:8000 — HTTP 200.

**Eklenen Dosyalar:**
- `app/Http/Controllers/KasaController.php`
- `app/Http/Controllers/HareketController.php`
- `resources/js/Pages/Kasa/Index.tsx`, `Form.tsx`
- `resources/js/Pages/Hareket/Index.tsx`, `Form.tsx`

**Bilinen Kısıtlar:**
- Kasalar arası transfer (bir kasadan diğerine para aktarma) henüz yok — Iter-3'te transfer modu eklenebilir.
- Hareket kaydı silindiğinde sadece soft delete uyarısı yok; bağlı PersonelOdeme/Belge kayıtları varsa nullOnDelete tetiklenir.
- Hareket düzenleme'de optimistic concurrency yok (Excel'de RowVersion vardı, web'de gerek görülmedi — iki kullanıcı aynı kaydı eş zamanlı düzenlemiyor).

**Test Notu:**
- Login → Cariler → cari ekle → Kasalar → kasa ekle (TL açılış 10.000) → Hareketler → tahsilat gir (cariden) → kasa bakiyesi otomatik güncellenir → cari bakiyesi güncellenir → Dashboard'a dön → kart değerleri yansır.

---

---

## 2026-05-07 — [Faz 1 / İterasyon 3] Personel Modülü (Mesai + Ödeme + Kasa Entegre)

**Yapılanlar:**

### 3 Yeni Controller
- `PersonelController.php` — index/create/store/edit/update/destroy + arama. Liste'de her personel için `netBorc()` hesaplanır (toplam mesai kazancı − toplam ödeme).
- `MesaiController.php` — bir personel için mesai listesi (tarih aralığı), ekleme, silme. Dönem özetinde çarpanlı toplam saat ve kazanç hesaplanır.
- `PersonelOdemeController.php` — **Kasa entegre transaction**: ödeme yapılınca aynı anda hem `personel_odemeleri`'ne hem `hareketler`'e (Yön=Borc, Tür="Personel Ödemesi" otomatik oluşur) yazılır. Silindiğinde bağlı kasa hareketi de DB::transaction içinde silinir.

### 4 Yeni Inertia Page
- `Personel/Index.tsx` — arama + tablo (Net Borç sütunu renkli), her satırda 4 ikon buton: Mesai (saat ikonu), Ödeme (cüzdan), Düzenle, Sil.
- `Personel/Form.tsx` — 3 panel (Kişisel + Ücret + İletişim) + Net Bakiye kartı (sadece düzenleme).
- `Personel/MesaiYonet.tsx` — üstte personel kartı + tarih filtresi + 2 özet kart (çarpanlı toplam saat, dönem kazancı). Mavi panelde **inline yeni mesai formu** (tarih + 4 saat alanı + açıklama). Tablo: 4 saat türü ayrı kolonlarda + çarpanlı toplam.
- `Personel/OdemeYonet.tsx` — üstte personel kartı + Net Borç (sağ üst). Tarih filtresi + 2 özet kart. Yeşil panelde **inline yeni ödeme formu** (tarih, kasa, tutar, ödeme türü, dönem, açıklama). Uyarı bandı: "kasaya borç hareketi olarak da işlenir". "+" butonu ile yeni ödeme türü prompt ile eklenir.

### Routes
- `personeller` resource (index/create/store/edit/update/destroy).
- `personeller/{personel}/mesai` (GET/POST) + delete.
- `personeller/{personel}/odeme` (GET/POST) + delete.
- `odeme-turleri` (POST) — yeni ödeme türü hızlı ekleme.

### Test Senaryosu (uçtan uca)
1. Login → Personeller → Yeni Personel: "Ahmet Yılmaz", saatlik 100 TL, günlük 8 saat.
2. Listede satırda Mesai ikonuna tıkla → Mesai Yönetimi açılır.
3. Mavi formdan: Bugün için 8 normal + 2 fazla mesai gir → Kaydet → liste güncellenir, dönem özeti: çarpanlı 11 saat, kazanç 1.100 TL.
4. Personeller → Ödeme ikonu → Ödeme Yönetimi.
5. Yeşil formdan: Merkez Kasa, 500 TL, "Avans" türü → Öde → kayıt eklenir, **Hareketler ekranında otomatik bir 'Personel Ödemesi' (Borç) hareketi oluşur**, **Merkez Kasa bakiyesi 500 TL düşer**.
6. Geri Personeller → Net Borç sütununda 600 TL ödenecek (1.100 kazanç − 500 ödeme).

**Eklenen Dosyalar:**
- `app/Http/Controllers/PersonelController.php`
- `app/Http/Controllers/MesaiController.php`
- `app/Http/Controllers/PersonelOdemeController.php`
- `resources/js/Pages/Personel/Index.tsx`, `Form.tsx`, `MesaiYonet.tsx`, `OdemeYonet.tsx`

**Bilinen Kısıtlar:**
- Personel ödemesi sadece TL bazlı (TutarTL = Tutar). Personel para birimi farklıysa kur dönüşümü Faz 3'te.
- Personel düzenleme'de mesai/ödeme listesi yok — ayrı sayfalar (Mesai/Ödeme ikon butonları).
- Ödeme türü silme arayüzü yok — Iter-4 (Kategori/Tür yönetimi) ile gelecek.

---

---

## 2026-05-07 — [Faz 1 / İterasyon 4] Modern Bileşenler + Tam Türkçeleştirme

**Yapılanlar:**

### Modern UI Bileşenleri
- **`Components/Select.tsx`** — Headless UI Listbox tabanlı, native `<select>` yerine. Özellikler:
  - Akıcı animasyon (Transition).
  - Aranabilir seçenek listesi (`searchable` prop, opsiyonel; arama input içinde label + description'ı tarar, TR locale insensitive).
  - Seçili öğe için Check ikonu, ChevronsUpDown collapse ikonu.
  - Description, icon, disabled per-option.
  - Error state desteği.
- **`Components/DatePicker.tsx`** — react-day-picker 9.x + date-fns + TR locale. Özellikler:
  - Popover'da takvim, ay/yıl dropdown'ları (2000-2050).
  - Pazartesi başlangıçlı hafta (TR standardı).
  - "Bugün" hızlı buton + "Temizle" (clearable mod için).
  - dd.MM.yyyy format gösterimi, ISO yyyy-MM-dd backend ile uyumlu.
  - Bugünün tarihi bold mavi, seçili gün koyu mavi arka plan.

### Bileşenlerin Uygulandığı Yerler
- **Cari Index/Form** — Kategori filtre + form Select.
- **Kasa Form** — Tip (Kasa/Banka/POS) + Para Birimi (₺/$/€) Select.
- **Hareket Form** — Tarih DatePicker, Tür/Kasa/Cari (searchable) + Para Birimi Select.
- **Hareket Index** — Tarih başlangıç/bitiş DatePicker, Cari (searchable) + Kasa Select.
- **Personel Form** — İşe Giriş Tarihi DatePicker (clearable), Para Birimi Select.
- **MesaiYonet** — Tarih filtresi + yeni kayıt tarih DatePicker.
- **OdemeYonet** — Tarih filtresi + yeni ödeme tarih + dönem tarihleri DatePicker (clearable), Kasa + Tür Select.

### Tam Türkçeleştirme
Tüm Auth ve Profile sayfaları sıfırdan yeniden yazıldı (Breeze default'unun yerine):
- **`GuestLayout.tsx`** — Modern login/auth sayfası iskeleti (gradient background, logo, kart, alt copyright).
- **`Auth/Login.tsx`** — "Hoş Geldiniz", "E-posta", "Şifre", "Beni hatırla", "Şifremi unuttum", "Giriş Yap". Mail/Lock ikonları input'larda.
- **`Auth/Register.tsx`** — "Hesap Oluştur", "Ad Soyad", "Şifre Tekrar", "Kayıt Ol".
- **`Auth/ForgotPassword.tsx`** — "Şifremi Unuttum", "Sıfırlama Bağlantısını Gönder".
- **`Auth/ResetPassword.tsx`** — "Şifre Sıfırla", "Yeni Şifre", "Şifreyi Sıfırla".
- **`Auth/ConfirmPassword.tsx`** — "Şifre Onayı", "Onayla".
- **`Auth/VerifyEmail.tsx`** — "E-posta Doğrulama", "Doğrulama E-postasını Tekrar Gönder".
- **`Profile/Edit.tsx`** — "Profil Ayarları" başlığı, AppLayout kullanımı.
- **`Profile/Partials/UpdateProfileInformationForm.tsx`** — "Profil Bilgileri", "Ad Soyad", "Kaydet".
- **`Profile/Partials/UpdatePasswordForm.tsx`** — "Şifre Güncelle", "Mevcut Şifre", "Yeni Şifre".
- **`Profile/Partials/DeleteUserForm.tsx`** — "Hesabı Sil" tehlike modali.

### Lokalizasyon
- `.env`: `APP_LOCALE=tr`, `APP_FALLBACK_LOCALE=tr`, `APP_FAKER_LOCALE=tr_TR`, `VITE_APP_NAME="Cari Takip Sistemi"`.
- HTML title: "Cari Takip Sistemi" (eski "Laravel" yerine).
- React Inertia title formatı: `${title} - Cari Takip Sistemi`.

### Yeni Bağımlılıklar
- `react-day-picker@9.14`
- `date-fns` (TR locale ile)

**Eklenen/Değişen Dosyalar:**
- `resources/js/Components/Select.tsx`, `DatePicker.tsx` (yeni)
- `resources/js/Layouts/GuestLayout.tsx` (yeniden yazıldı)
- `resources/js/Pages/Auth/*.tsx` (6 dosya, hepsi Türkçe)
- `resources/js/Pages/Profile/Edit.tsx`, `Partials/*.tsx` (3 dosya)
- `resources/js/Pages/Cari/*.tsx`, `Kasa/*.tsx`, `Hareket/*.tsx`, `Personel/*.tsx` (Select/DatePicker entegre)
- `.env` (locale + VITE_APP_NAME)

**Bilinen Kısıtlar:**
- Inertia client-side render olduğu için curl ile gönderilen sayfada Türkçe içerik görünmez (sadece JSON props + JS bundle döner). Tarayıcıda doğru görünür.
- Welcome.tsx kullanılmıyor ama hâlâ bundle'da (auth varsa redirect ediyor). İleride kaldırılabilir.

**Test Notu:**
- http://127.0.0.1:8000/login → modern gradient arka plan, beyaz kart, logo. "Hoş Geldiniz / Devam etmek için giriş yapınız" alt yazı.
- Cari ekleme formunda kategori dropdown'u tıklayınca akıcı açılır, ChevronsUpDown ikonu döner.
- Hareketler sayfasında tarih alanına tıklayınca takvim popover'i açılır; ay/yıl dropdown'larıyla hızlı geçiş, Pazartesi başlangıçlı hafta.

---

---

## 2026-05-07 — [Faz 1 / İterasyon 5] MoneyInput + Audit Log + Detaylı Yetki + 5 Yönetim Ekranı

**Yapılanlar:**

### Hızlı Bug Fix'ler
- **DatePicker overlap düzeltildi** — react-day-picker 9 default class'ları kullanıcı CSS ile çakışıyordu. Inline `<style>` ile `.rdp-cari` namespace altında temiz tablo grid (flex tabanlı haftalar, 2.25rem hücre boyutu, sticky caption). Ay/yıl dropdown'ları absolute positioned overlay olarak düzeltildi.
- **Profile'dan "Hesabı Sil" kaldırıldı.** Yerine bilgi notu: "Hesap silme sadece sistem yöneticilerinde, talep için yöneticinizle iletişime geçin."

### MoneyInput
- `Components/MoneyInput.tsx` — TR locale para input'u.
  - **Sadece sayı + virgül + nokta** kabul eder; harf, semboller engellenir (keyDown filter).
  - Yazarken serbest, **blur olunca otomatik formatlar**: `200000` → `200.000,00`, `50,5` → `50,50`, `1234.56` → `1.234,56`.
  - Sol başında para birimi sembolü (₺/$/€), sağa hizalı sayı.
  - `parseTR()` heuristik: hem `.` hem `,` varsa nokta=binlik/virgül=ondalık; sadece `.` varsa son nokta sonrasında ≤2 hane ondalık say, değilse binlik.
- Uygulanan alanlar: Kasa açılış bakiyesi, Hareket tutar, Personel saatlik ücret, Personel ödeme tutarı.

### Audit Log Altyapısı
- `Models/SistemLog.php` — Eloquent model, JSON `detay` cast.
- `Services/AuditLog.php` — `yaz()`, `modelOlustu()`, `modelGuncellendi()`, `modelSilindi()` static helper'lar. Kullanıcı, IP, eski/yeni değerler kaydedilir.
- `Observers/AuditObserver.php` — created/updated/deleted event'lerini yakalar. 13 model için bind: Cari, Kasa, Hareket, Personel, Mesai, PersonelOdeme, Firma, User, Rol, Yetki, kategoriler.
- `AppServiceProvider::boot()`'ta `$class::observe(AuditObserver::class)` ile otomatik bağlanır.

### Detaylı Yetki Sistemi
- `Support/Permissions.php` — sistem genelinde tek doğru kaynak. **11 modül × ortalama 4-6 işlem = ~50 izin** anahtarı.
  - Modüller: cari, kasa, hareket, personel, firma, kullanici, rol, kategori, rapor, log, ayarlar.
  - Özel işlemler: `hareket.kendi_goruntule`, `personel.mesai`, `personel.odeme`, `kullanici.rol_ata`, `cari.ihrac`, vb.
  - 5 default rol matrisi: Admin (tümü), Yönetici (kullanıcı/rol silemez), Muhasebe (finansal tam yetki + yönetim okuma), Çalışan (sınırlı), Görüntüleyici (sadece okuma).
- `Middleware/YetkiKontrol.php` — route koruması: `->middleware('yetki:cari.ekle')`. `bootstrap/app.php`'de `'yetki'` alias kayıtlı.
- `User::yetkiKontrol('cari.ekle')` — admin → true, değilse rol yetkilerinden eşleşme. `yetkiHaritasi()` Inertia paylaşımı için tüm izinleri dict olarak döner.
- `HandleInertiaRequests` middleware — auth user + is_admin + yetkiler + firmalar + aktif_firma + flash mesaj paylaşır.
- `lib/yetki.ts` — `useYetki()` hook'u. `can('cari.ekle')`, `canAny()`, `canAll()`. UI tarafında buton/menü gösterimleri yetki bazlı.

### 5 Yeni Yönetim Ekranı
- **Firma Yönetimi** (`Pages/Firma/Index.tsx`, `Form.tsx`) — Kart tabanlı liste, **aktif firma vurgusu** (mavi border), her kart altında cari/kasa/personel sayıları. "Aktif Yap" butonu ile firma değiştirilir.
- **Kullanıcı Yönetimi** (`Pages/Kullanici/Index.tsx`, `Form.tsx`) — Tablo görünümü. Form'da rol seçimi + **çoklu firma yetkisi** (checkbox grid).
- **Rol Yönetimi** (`Pages/Rol/Index.tsx`, `Form.tsx`) — Rol kartları + kullanıcı sayısı. Form'da **yetki matrisi UI**: her modül kendi accordion-vari kart, 4 sütunlu izin checkbox grid, "Tümünü Seç" / "Hepsini Kaldır" hızlı butonları.
- **Kategori/Tür Yönetimi** (`Pages/Kategori/Index.tsx`) — Tek ekranda **3 sekme**: Cari Kategorileri, Hareket Türleri, Ödeme Türleri. Her sekmede inline ekleme bar'ı + grid liste + sil butonu.
- **Günlük Kayıtları** (`Pages/SistemLog/Index.tsx`) — Tarih + kullanıcı + modül + işlem filtreli, sayfalanmış log listesi. İşlem tipine göre renkli badge (create=yeşil, update=mavi, delete=kırmızı, login=sarı).

### Yeni Üst Bar
- AppLayout'a **firma seçici dropdown** eklendi (sağ üstte). Aktif firmaya checkmark, listede tıklayarak değiştirme. Listenin altında "Firmaları yönet →" link'i.
- Sol panelde **Yönetim** alt-başlığı altında yetki bazlı menü (Firmalar, Kullanıcılar, Roller, Kategoriler, Günlük). Yetki yoksa menü görünmez.
- Profil dropdown'ı zenginleştirildi: avatar, ad, email, **rol badge'i**.

### Routes
- 3 yeni resource: firmalar, kullanicilar, roller — hepsi `yetki:` middleware ile.
- Kategori için 9 endpoint (3 grup × ekle/güncelle/sil).
- `gunluk-kayitlar` GET — `log.goruntule` korumalı.
- `firma-aktif/{firma}` POST — aktif firma değiştirme.

### Default Veri (Seeder)
- `migrate:fresh --seed` ile sıfırlandı: **5 rol** (Admin/Yönetici/Muhasebe/Çalışan/Görüntüleyici) + her rol için modul başına 4 sütunlu yetki kayıtları + admin user (admin@cari.local/admin123) + örnek firma + default kategoriler/türler.

**Eklenen/Değişen Dosyalar:**
- `app/Http/Controllers/{Firma,Kullanici,Rol,Kategori,SistemLog}Controller.php` (5)
- `app/Http/Middleware/{HandleInertiaRequests,YetkiKontrol}.php`
- `app/Models/SistemLog.php`, `User.php` (yetki metodları)
- `app/Observers/AuditObserver.php`
- `app/Services/AuditLog.php`
- `app/Support/Permissions.php`
- `app/Providers/AppServiceProvider.php` (Observer bağlama)
- `bootstrap/app.php` (middleware alias)
- `database/seeders/DatabaseSeeder.php` (yeni rol matrisi)
- `resources/js/Components/{MoneyInput,DatePicker}.tsx` (DatePicker rewrite)
- `resources/js/Layouts/AppLayout.tsx` (sidebar yetki + firma seçici)
- `resources/js/lib/yetki.ts`
- `resources/js/Pages/{Firma,Kullanici,Rol,Kategori,SistemLog}/*.tsx` (8 yeni)
- `resources/js/Pages/Profile/Edit.tsx` (DeleteUserForm kaldırıldı)
- `routes/web.php` (yetki middleware + 5 yönetim resource)

**Bilinen Kısıtlar / TODO:**
- Yetki sütunları bizim tabloda hâlâ 4 (goruntule/ekle/duzenle/sil). Genişletilmiş işlemler (ihrac, kendi_goruntule, mesai, odeme) bu 4'e eşleniyor — ileride tabloya `meta_izinler JSON` kolonu eklenebilir.
- Audit log şu an sadece Eloquent model değişikliklerini yakalıyor. Login/logout için manuel `AuditLog::yaz()` çağrısı eklenmeli (Authenticated/LoggedOut event listener).
- Permission UI'ında "tüm rolleri matrise göre toplu düzenleme" ekranı yok (her rol ayrı düzenlenir).

**Test Notu:**
- `migrate:fresh --seed` ile sıfırdan kuruldu.
- Login admin@cari.local/admin123 → sol menüde tüm "Yönetim" başlıkları görünür. Üst bar firma seçici dropdown'ı çalışır.
- Roller → Yeni Rol → "Stajyer" oluştur, sadece `cari.goruntule` aç. Kullanıcılar → Yeni → bir kullanıcıya bu rolü ata. O kullanıcıyla giriş yap → soldaki menüde sadece "Cariler" görünür, ekle/sil butonları yok.
- Kategoriler → "Hareket Türleri" sekmesi → Yeni "İade" türü ekle. Hareket Form'unda dropdown'da görünür.
- Günlük Kayıtları → admin'in tüm CRUD işlemleri kullanıcı + IP + eski/yeni değer JSON ile listelenir.

---

---

## 2026-05-07 — [Faz 1 / İterasyon 6] Pagination TR + Para Birimi + Toplu Hareket + Raporlar (PDF/Excel)

**Yapılanlar:**

### 1. Pagination Türkçeleştirme
- `lang/tr/pagination.php` + `lang/tr/validation.php` oluşturuldu. APP_LOCALE=tr olduğu için Laravel paginator artık "&laquo; Önceki" / "Sonraki &raquo;" döner.

### 2. Para Birimi Yönetimi
- Yeni `para_birimleri` tablosu: kod (TRY/USD/EUR/...), ad, sembol, varsayılan, aktif, sira.
- `ParaBirimi` model + `ParaBirimiController` (CRUD).
- `Pages/ParaBirimi/Index.tsx` — kart bazlı görünüm + inline ekleme formu + yıldız ikonu ile varsayılan seçimi.
- Admin/Yönetici eklediği para birimi tüm Select'lerde anında görünür (Inertia share `para_birimleri`).
- `lib/paraBirimi.ts` — `useParaBirimleri()` hook'u, tüm formlar (Cari, Kasa, Hareket, Personel, Toplu Hareket, MoneyInput) buradan dinamik liste alır.

### 3. "Kaydet ve Yeni Ekle" Butonu
- Cari/Kasa/Hareket/Personel form'larına eklendi. Tıklanınca: kayıt + `/x/create` redirect (boş form ile yeni giriş).
- Sadece yeni kayıt modunda görünür (düzenleme modunda yok).

### 4. Toplu Hareket Ekleme
- `HareketController::topluForm/topluKaydet`. DB transaction içinde N satır.
- `Pages/Hareket/Toplu.tsx`: tablo görünümü, başlangıçta 3 satır. Her satırda DatePicker, Select (Tür/Kasa/Cari), MoneyInput, Kur, Belge No, Açıklama. Boş satırlar kaydederken atlanır.
- Satır kopyalama (Copy ikonu — aynı satırın altına yeni satır), satır silme. Alt çubukta canlı "satır sayısı" + "net TL" özetlenir.
- Hareket Index sayfasına **"Toplu Ekle"** butonu (yeşil çerçeveli).

### 5. Raporlar Modülü
- `composer require barryvdh/laravel-dompdf` (PDF üretimi).
- `RaporController` 4 rapor + her biri PDF + bazıları Excel (CSV) export:
  - **Cari Ekstresi**: cari + tarih aralığı → önceki bakiye + dönem hareketleri (borç/alacak/bakiye sütunları) + toplam. PDF + CSV.
  - **Kasa Raporu**: kasa + tarih aralığı → dönem başı bakiye + giriş/çıkış toplamları + son bakiye. PDF + CSV.
  - **Mali Özet**: tarih aralığı → tür bazlı toplamlar + para birimi bazlı kasa toplamları + net sonuç. PDF.
  - **Personel Bordrosu**: personel + tarih aralığı → mesai dökümü + ödeme dökümü + toplam saat/kazanç/ödeme/kalan bakiye. PDF.
- 4 Inertia sayfası (`Pages/Rapor/{Index,CariEkstresi,KasaRaporu,MaliOzet,PersonelBordrosu}.tsx`).
- 4 Blade view (`resources/views/raporlar/*.blade.php`) + `_layout.blade.php` ortak şablonu (DejaVu Sans, A4, başlık, footer, badge'ler).
- CSV export: BOM + UTF-8 + TR formatı (`;` separator, `1.234,56`).

### 6. Sidebar Yenileme
- "Raporlar" link'i ana navigation'a eklendi (yetki: rapor.goruntule).
- Yönetim altına "Para Birimleri" eklendi (yetki: ayarlar.goruntule).

**Eklenen/Değişen Dosyalar:**
- `lang/tr/{pagination,validation}.php` (yeni)
- `database/migrations/2026_05_07_000002_create_para_birimleri.php`
- `app/Models/ParaBirimi.php`
- `app/Http/Controllers/{ParaBirimiController,RaporController}.php`
- `app/Http/Controllers/HareketController.php` (topluForm/topluKaydet)
- `app/Http/Middleware/HandleInertiaRequests.php` (para_birimleri share)
- `database/seeders/DatabaseSeeder.php` (3 default para birimi)
- `resources/js/Components/Pagination*` — yok, mevcut sayfalar kullanır
- `resources/js/lib/paraBirimi.ts`
- `resources/js/Pages/ParaBirimi/Index.tsx`
- `resources/js/Pages/Hareket/Toplu.tsx`
- `resources/js/Pages/Rapor/{Index,CariEkstresi,KasaRaporu,MaliOzet,PersonelBordrosu}.tsx` (5 dosya)
- `resources/js/Pages/{Cari,Kasa,Hareket,Personel}/Form.tsx` ("Kaydet ve Yeni Ekle" butonu + dinamik para birimi)
- `resources/js/Layouts/AppLayout.tsx` (Raporlar + Para Birimleri menü)
- `resources/views/raporlar/{_layout,cari-ekstresi,kasa-raporu,mali-ozet,personel-bordrosu}.blade.php`
- `routes/web.php` (toplu, para-birimleri, raporlar/*)
- `composer.json` (barryvdh/laravel-dompdf)

**Bilinen Kısıtlar:**
- Excel export şu an gerçek `.xlsx` değil, **UTF-8 BOM + TR locale CSV** (Excel doğru açar, ; ayraç). İleride maatwebsite/excel ile gerçek xlsx eklenebilir.
- "Kaydet ve Yeni Ekle" sadece **POST onSuccess** sonrası redirect; validation hatası varsa form ekranında kalır (doğru davranış).
- Toplu hareket'te kur otomatik 1 (TRY için), USD/EUR için manuel girilmeli — TCMB API entegrasyonu gelecek.

**Test Notu:**
- migrate (yeni para_birimleri tablosu eklendi) + seed (3 default para birimi).
- http://127.0.0.1:8000 → admin@cari.local / admin123.
- Yönetim → Para Birimleri → "GBP, £, İngiliz Sterlini" ekle → Kasa formunda dropdown'da görünür.
- Hareketler → "Toplu Ekle" → 5 farklı hareket bir arada gir → "Hareketi Kaydet" → 5 hareket aynı transaction'da kaydedilir, listede görünür.
- Raporlar → Cari Ekstresi → cari seç + tarih aralığı + Hazırla → tabloyu gör → "PDF İndir" / "Excel İndir" butonları çalışır.
- Sayfalama'da "&laquo; Önceki" / "Sonraki &raquo;" düzgün TR.

---

---

## 2026-05-07 — [Faz 1 / İterasyon 7] TCMB Otomatik Efektif Satış Kuru + Kâr Marjı

**Yapılanlar:**

### 1. Migration
- `para_birimleri` tablosuna 7 yeni kolon: `tcmb_kod`, `kur_tipi` (efektif_alis/efektif_satis/doviz_alis/doviz_satis), `kar_miktari`, `kar_tipi` (yuzde/sabit), `son_kur`, `son_kur_tarihi`, `otomatik_guncelle`.

### 2. TCMB Service
- `app/Services/TcmbKurService.php` — `https://www.tcmb.gov.tr/kurlar/today.xml` XML'inden 4 kur tipini parse eder:
  - **ForexBuying** → Döviz Alış (transfer)
  - **ForexSelling** → Döviz Satış (transfer)
  - **BanknoteBuying** → Efektif Alış (nakit)
  - **BanknoteSelling** → Efektif Satış (nakit) ← varsayılan
- Para birimi başına `Unit` (örn. JPY 100 birim) otomatik handle edilir.
- `paraBirimiGuncelle()` — tek para birimi güncelle; kâr marjı uygulanır.
- `tumunuGuncelle()` — `otomatik_guncelle=true` olan tüm para birimleri.

### 3. Kâr Marjı Uygulama
- `ParaBirimi::kuraKarUygula($hamKur)`:
  - **yuzde**: `hamKur × (1 + miktar/100)` → "TCMB satış + %2"
  - **sabit**: `hamKur + miktar` → "TCMB satış + 0,10 TL"

### 4. Artisan Command
- `php artisan kur:guncelle` — Service'i çağırır, sonuçları konsola basar, AuditLog'a kaydeder.
- Hata durumunda exit code 1.

### 5. Schedule
- `routes/console.php`:
  ```php
  Schedule::command('kur:guncelle')
      ->timezone('Europe/Istanbul')
      ->dailyAt('15:30')
      ->withoutOverlapping();
  ```
- `php artisan schedule:list` ile doğrulandı: UTC 12:30 = TR 15:30 ✓

### 6. Controller Genişletme
- `simdiGuncelle(ParaBirimi)` — tek para birimi anında çek.
- `tumunuGuncelle()` — toplu güncelleme butonu.

### 7. Para Birimi UI Genişletme
- Tabloda: TCMB Kodu, Kur Tipi, Kâr Marjı, Son Kur, Son Tarih, Otomatik durumu kolonları.
- Sağ üstte **"TCMB'den Şimdi Güncelle (N)"** primary butonu (otomatik_guncelle açık olan sayısı).
- Her satırda <RefreshCw /> butonu — o para birimini anında güncelle.
- <Settings /> butonu → **Modal** açılır:
  - Genel: Kod / Sembol / Sıra / Ad / Aktif / Varsayılan
  - **TCMB Otomatik Kur Bölümü** (sarı/mavi panel):
    - TCMB Kodu input
    - Kur Tipi select (4 seçenek)
    - Kâr Miktarı + Kâr Tipi (yüzde/sabit)
    - "Otomatik Güncelleme" checkbox (her gün 15:30)
    - **Canlı önizleme**: "TCMB kuru X ise kayıtlı kur: X × 1.0200" gibi.

### 8. Hareket Form'unda Otomatik Kur
- `useParaBirimleri()` hook'una `kurAl(kod)` eklendi — kod alıp son_kur döner (TRY için 1).
- Hareket Form: para birimi dropdown'ı değiştiğinde `setData('kur', kurAl(kod))` ile **kur otomatik dolar**.
- Toplu Hareket sayfasında da aynı davranış her satır için.
- Inertia share `para_birimleri` artık `son_kur` + `son_kur_tarihi` da içeriyor.

### 9. Cron Talimatı (`docs/cron-kurulum.md`)
- **Production cPanel**: `* * * * * cd /home/user/public_html && php artisan schedule:run >> /dev/null 2>&1`
- **Windows lokal**: Görev Zamanlayıcı (her dakika) veya `php artisan schedule:work` (foreground).
- `php artisan schedule:list` ile doğrulama.

**Eklenen/Değişen Dosyalar:**
- `database/migrations/2026_05_07_000003_para_birimleri_tcmb_alanlar.php`
- `app/Models/ParaBirimi.php` (genişletilmiş)
- `app/Services/TcmbKurService.php` (yeni)
- `app/Console/Commands/KurGuncelle.php` (yeni)
- `app/Http/Controllers/ParaBirimiController.php` (TCMB action'lar)
- `app/Http/Middleware/HandleInertiaRequests.php` (son_kur paylaşımı)
- `routes/console.php` (Schedule)
- `routes/web.php` (yeni endpoint'ler)
- `resources/js/lib/paraBirimi.ts` (kurAl helper)
- `resources/js/Pages/ParaBirimi/Index.tsx` (modal + TCMB ayarları)
- `resources/js/Pages/Hareket/Form.tsx` (otomatik kur)
- `resources/js/Pages/Hareket/Toplu.tsx` (otomatik kur)
- `database/seeders/DatabaseSeeder.php` (USD/EUR varsayılan TCMB ayarlı)
- `docs/cron-kurulum.md` (yeni)

**Test Notu:**
- `php artisan kur:guncelle` çalıştırıldı, **gerçek TCMB API'den kurlar çekildi**:
  - USD: 45,3058 TL
  - EUR: 53,3060 TL
- `php artisan schedule:list` → "30 12 * * * kur:guncelle" görünüyor (UTC 12:30 = İstanbul 15:30 ✓).
- Para Birimleri sayfasında TCMB Kodu/Kur Tipi/Kâr Marjı görünür, **Settings butonuyla modal** açılır.
- Hareketler → Yeni → para birimi USD seç → kur 45.3058 otomatik gelir.

**Bilinen Kısıtlar:**
- Lokal Windows'ta `php artisan schedule:run` her dakika çalıştırılmadığı sürece otomatik tetiklenmez. Görev Zamanlayıcı kurulumu gerek (talimat: `docs/cron-kurulum.md`).
- TCMB internet erişimi gerektirir; offline'da hata loglanır, son_kur değişmez.
- Hafta sonu/resmi tatillerde TCMB yeni bülten yayınlamaz; eski kur kullanılır (TCMB XML zaten son işgünü kurunu döner).

---

---

## 2026-05-07 — [Faz 1 / İterasyon 8] Modern ConfirmDialog (Native confirm() Yerine)

**Yapılanlar:**
- `Components/ConfirmDialog.tsx` — Headless UI Dialog tabanlı **promise-based** modal. 5 tip (soru/tehlike/uyari/bilgi/basari), her birinde farklı ikon + renk.
- `<ConfirmProvider>` + `useConfirm()` hook'u — global Context.
- `app.tsx`'e Provider sarıldı.
- **14 sayfadaki tüm `confirm()` çağrıları** modern dialog'a çevrildi: Cari, Firma, Hareket (Index + Toplu), Kasa, Personel (Index + MesaiYonet + OdemeYonet), Kategori (3 sekme), Kullanici, Rol, ParaBirimi.
- Her silme işleminde **ekstra bilgi**: cari adı bold, hareket tarih+tutar, "Mesai/ödeme varsa pasife alınır" detay açıklaması.
- Animasyonlu giriş/çıkış (scale + fade), backdrop blur efekti, Enter ile onay (autoFocus), ESC/backdrop ile iptal.
- Inertia progress bar rengi: gri → mavi (#3b82f6).

**Test:**
- Herhangi bir liste → Sil → Modern modal açılır: ikon + başlık + bold isim + alt açıklama + İptal/Sil.
- ESC, backdrop click, X butonu = iptal.
- Enter = onay (autoFocus üzerinde).

---

---

## 2026-05-07 — [Faz 1 / İterasyon 9] FileManager — Cari/Hareket/Personel'e Belge Ekleme

**Yapılanlar:**

### Backend
- **`Belge` model** — polymorphic relation (`iliski` morphTo). `belgeler` tablosu zaten vardı, kullanıldı.
- **Cari/Hareket/Personel** model'lerine `belgeler()` MorphMany relation'ı eklendi.
- **`BelgeController`** — 4 action:
  - `index($tip, $id)` → JSON belge listesi (önizleme + indirme URL'leri ile).
  - `store($tip, $id)` → çoklu dosya upload (max 10 dosya × 20 MB).
  - `download(Belge)` → orijinal ad ile indir.
  - `destroy(Belge)` → storage + DB'den sil.
- **Yetki kontrolü**: aktif firma izolasyonu + `{tip}.goruntule` izni şart.
- **Routes** (auth altında):
  - `GET /belgeler/{tip}/{id}` — liste (cari/hareket/personel)
  - `POST /belgeler/{tip}/{id}` — yükleme
  - `GET /belgeler/{belge}/indir` — indirme
  - `DELETE /belgeler/{belge}` — silme
- **`storage/app/public/belgeler/{tip}/{id}/...`** dizinine kaydeder; dosya adları **rastgele 20 karakter + uzantı** (güvenlik için).
- **`php artisan storage:link`** çalıştırıldı → `public/storage` symlink hazır.

### FileManager Component (`resources/js/Components/FileManager.tsx`)
**Şık + işlevsel.** Özellikler:

- **Sürükle-bırak** alanı: hover'da mavi vurgu, dosya bırakınca yükleme.
- **Tıkla** → klasik file picker (çoklu dosya seçimi).
- **Açıklama input'u** — yüklenecek tüm dosyalar için ortak.
- **Yükleme indicator** (Loader2 spinner).
- **Belge kartları** (3 kolonlu grid):
  - Resim ise küçük thumbnail.
  - PDF/Word/Excel/Video/diğer için **uzantıya göre renkli ikon** (FileText/FileSpreadsheet/FileImage/FileVideo/File).
  - Dosya adı + boyut (KB/MB) + yüklenme tarihi.
  - Açıklama (italic, küçük).
  - Hover'da görünen ikonlar: **İndir** + **Sil**.
- **Önizleme modal'ı** (büyük overlay):
  - Resim → tam boyut görüntü.
  - PDF → iframe ile gömülü görüntü.
  - Word/Excel/diğer → "indir" butonu (tarayıcıda önizleme yok).
  - Backdrop click veya X = kapanır.
- **Sil onayı** — `useConfirm()` ile modern dialog ("Bu dosya kalıcı olarak silinir").
- **Compact mod** prop'u (küçük kullanım için başlıksız).

### Form Entegrasyonları
Tüm form'larda **sadece düzenleme modunda** (kayıt oluştuktan sonra) FileManager görünür:
- **Cari/Form.tsx** — Bakiye paneli üstüne.
- **Hareket/Form.tsx** — Özet paneli sonrasına.
- **Personel/Form.tsx** — Net Bakiye kartı üstüne.

Yeni kayıt ekleme modunda FileManager görünmez (önce kayıt oluşmalı, sonra ID üzerinden upload yapılabilir).

**Eklenen/Değişen Dosyalar:**
- `app/Models/Belge.php` (yeni)
- `app/Models/Cari.php`, `Hareket.php`, `Personel.php` (`belgeler()` relation eklendi)
- `app/Http/Controllers/BelgeController.php` (yeni)
- `routes/web.php` (4 belge endpoint'i)
- `resources/js/Components/FileManager.tsx` (yeni)
- `resources/js/Pages/Cari/Form.tsx`, `Hareket/Form.tsx`, `Personel/Form.tsx` (entegrasyon)
- `public/storage` symlink (php artisan storage:link)

**Test Notu:**
- Cariler → herhangi bir cariyi düzenle → sayfanın altında **"Belgeler" paneli**.
- Drop zone'a fatura PDF'i sürükle bırak → yüklenir → liste güncellenir.
- Listedeki belgeye tıkla → modal'da PDF iframe açılır.
- Hover'da **indir** ikonuna bas → tarayıcı orijinal adla indirir.
- **Sil** ikonu → modern modal "Belge kalıcı silinir" → onayla → silinir.
- Hareket düzenleme'de fatura/dekont, Personel düzenleme'de CV/sözleşme yüklenebilir.

**Bilinen Kısıtlar:**
- Maks. 20 MB / dosya, 10 dosya / yükleme. PHP `upload_max_filesize` ve `post_max_size` `php.ini`'de yetmezse Laragon menüsünden artırılmalı (`PHP → php.ini`).
- Tarayıcıda PDF/resim önizleme dışında dosya tipleri yalnızca indirilebilir.
- Belge düzenleme yok (yeniden yükleyip eskiyi sil).
- Çoklu seçim/toplu silme yok (Iter-10'da eklenebilir).

---

---

## 2026-05-07 — [Faz 1 / İterasyon 10] Toplu İşlem (Bulk Selection)

**Yapılanlar:**

### Reusable Bileşenler
- **`lib/bulkSelection.ts`** — `useBulkSelection<T>(items, getId)` hook'u. Set tabanlı state, toggle/toggleAll/clear/isSelected/allSelected/someSelected/count/ids API'si.
- **`Components/IndeterminateCheckbox.tsx`** — Üç durumlu checkbox (hepsi/kısmen/hiç) — header'da "Tümünü seç".
- **`Components/BulkActionBar.tsx`** — Seçim varken **sticky alt bar** (yumuşak fade+slide animasyon). "X seçili" rozeti + custom action butonları + temizleme X.

### Backend Toplu İşlem
- `POST /cariler/toplu-sil` — Hareket varsa pasife, yoksa hard delete. Mesaj: "X silindi, Y pasife alındı".
- `POST /hareketler/toplu-sil` — Hard delete (kasa bakiyeleri yeniden hesaplanır).
- `POST /personeller/toplu-sil` — Mesai/ödeme varsa pasife, yoksa hard delete.
- `POST /kullanicilar/toplu-pasif` — Toplu pasife alma; aktif kullanıcı kendi ID'sini listeden çıkarır.
- Hepsinde `yetki:{modul}.sil` izni şart.

### 4 Ana Listede Entegrasyon
- **Cari/Hareket/Personel/Kullanıcı Index** — checkbox sütunu (sol baş), seçili satırlar mavi vurguyla, header'da indeterminate checkbox.
- **Modern Confirm Dialog** ile toplu silme onayı (kaç kayıt + detay açıklama).
- Kullanıcı listesinde **aktif kullanıcı checkbox göstermez** (kazara pasife almaktan korunur).
- Sayfa değiştirince/filtreleyince seçim sıfırlanır.

**Eklenen/Değişen Dosyalar:**
- `resources/js/lib/bulkSelection.ts`
- `resources/js/Components/{IndeterminateCheckbox,BulkActionBar}.tsx`
- `app/Http/Controllers/{Cari,Hareket,Personel,Kullanici}Controller.php`
- `routes/web.php`
- `resources/js/Pages/{Cari,Hareket,Personel,Kullanici}/Index.tsx`

**Test:**
- Header checkbox → tüm satır seçilir → sticky bar görünür → "Seçilenleri Sil" → modern onay → silinir → bar gizlenir.
- Bazı seçim → header indeterminate (yarı dolu kare).
- Kullanıcılar'da kendi satırınızdaki checkbox görünmez.

---

---

## 2026-05-07 — [Faz 1 / İterasyon 11] Güvenlik Sertleştirme (Security Hardening)

13 kritik güvenlik kontrolü eklendi.

### Auth & Şifre
- **Login rate limit**: 5 başarısız deneme/dk (IP+email per).
- **Brute force kilit**: 10 başarısız → hesap 15 dk kilitli.
- **Pasif hesap engelleme**: aktif=false giriş reddedilir.
- **Güçlü şifre**: `app/Rules/GuvenliSifre.php` — min 10 + büyük/küçük + rakam.
- **Şifre history**: `sifre_gecmisi` tablosu, son 3 şifre tekrar kullanılamaz.
- Profile + Kullanıcı yönetimi şifre değiştirmede aynı politika.

### Audit
- **Login event'leri**: başarılı, başarısız, kilit aktif, geçersiz email, rate limit, hesap kilitleme — `sistem_loglari`'na (IP + UA ile).
- **Logout** event'i loglu.

### Session & Idle Timeout
- `.env`: `SESSION_LIFETIME=30`, `SESSION_ENCRYPT=true`, `SESSION_SAME_SITE=lax`.
- **Frontend `IdleTimeout` component**: 28 dk → uyarı dialog ("2 dk içinde çıkacaksınız"); 30 dk → otomatik logout. Mouse/klavye/scroll/touch sıfırlar.

### Network
- **`ForceHttps` middleware** (production): http→https 301, URL forceScheme.
- **`GuvenlikHeaders` middleware**: X-Frame-Options=SAMEORIGIN, X-Content-Type-Options=nosniff, Referrer-Policy, Permissions-Policy (camera/mic/geolocation kapalı), CSP, HSTS (production HTTPS).
- **Global rate limit**: `throttleApi()` (DoS koruma).

### File Upload Güvenliği
- **Whitelist** (pdf/jpg/png/word/excel/zip vb.).
- **Tehlikeli uzantı blacklist**: php/exe/js/svg/htm reddedilir.
- **MIME whitelist** (gerçek içerik).
- **Çift uzantı kontrolü**: `virus.pdf.php` yakalanır.
- **Magic bytes kontrolü**: PDF/PNG/JPEG/ZIP/OLE2 imza eşleşmesi.
- Açıklayıcı hata mesajları.

### Belge Erişim Kontrolü
- `Belge::url` artık `route('belgeler.preview', id)` — controller üzerinden yetki kontrollü.
- 2 endpoint: `preview` (inline) + `download` (attachment).
- `storage/app/public/belgeler/.htaccess`: PHP/JS/HTML execution Apache seviyesinde engellenir.

### KVKK
- **`Personel.tc` encrypted cast**: AES ile şifreli saklama.
- **KVKK Aydınlatma Metni** sayfası (`/kvkk`): işlenen veriler, amaçlar, güvenlik önlemleri, KVKK Madde 11 hakları. Login öncesi erişilebilir.
- GuestLayout footer'a link.

### Migration
- `users` tablosuna: `basarisiz_giris_sayisi`, `kilit_bitis`, `sifre_degisme_tarihi`.
- Yeni `sifre_gecmisi` tablosu.

**Test:**
- HTTP response header'larında doğrulandı: X-Frame-Options, X-Content-Type-Options, Referrer-Policy, CSP ✓
- Migration + build başarılı.

**Bilinen Kısıtlar:**
- **2FA (TOTP) yok** — `pragmarx/google2fa-laravel` paketi gerekiyor, ayrı tura.
- **Personel TC encryption** mevcut kayıtları dönüştürmez (üretim öncesi `migrate:fresh` veya manuel script).
- HTTPS zorlama sadece `APP_ENV=production`'da etkin.
- `.htaccess` Apache için; Nginx ayrı config gerekir.

---

---

## 2026-05-07 — [Faz 1 / İterasyon 12] Production Deploy Paketi (cPanel)

### Otomatik Paketleme: `deploy-paketle.ps1`
Tek komutla deploy ZIP'i hazırlar:
1. `composer install --no-dev --optimize-autoloader`
2. `npm run build`
3. `php artisan db:aktar-mysql` (SQLite → MySQL SQL dump)
4. Hassas dosyaları (`.env`, `node_modules`, tests, logs) **hariç tutarak** `cari-takip-deploy.zip` üretir
5. `data.sql` + `.env.production.example` + `KURULUM.md` aynı `deploy/` klasörüne
6. Lokal dev paketleri otomatik restore edilir.

### `php artisan db:aktar-mysql` Command
- `app/Console/Commands/DbAktarMysql.php` — SQLite verisini **MySQL uyumlu** TRUNCATE + INSERT batch'leri ile SQL dump'a çıkarır.
- 100'erli batch (büyük tablolar için).
- `SET FOREIGN_KEY_CHECKS=0;...=1;` ile FK sıralaması sorun çıkarmaz.
- Sistem tabloları (cache/sessions/jobs/migrations) hariç tutulur.

### `.env.production.example` Şablonu
- `APP_ENV=production`, `APP_DEBUG=false`
- MySQL placeholder'ları
- `SESSION_DRIVER=database`, `SESSION_SECURE_COOKIE=true`, `SESSION_SAME_SITE=strict`
- `LOG_LEVEL=warning`, daily rotation
- SMTP mail config örneği

### `docs/cpanel-deploy.md` — 15 bölümlü kılavuz
- Ön gereksinimler (PHP 8.2+, MySQL 8.0+, SSH önerilir)
- Lokal paketleme akışı
- cPanel MySQL DB oluşturma
- Dosya yükleme (File Manager / FTP)
- Document root yönlendirme (subdomain / symlink)
- `.env` + APP_KEY üretimi
- SQL import (phpMyAdmin / SSH)
- Laravel kurulum komutları (`migrate --force`, `config:cache`, `storage:link`)
- TCMB cron (`* * * * * php artisan schedule:run`)
- HTTPS force + SSL sertifikası
- İlk giriş & test
- Üretim sertleştirme checklist
- Güncelleme süreci (`-DbAktarmaAtlA` flag)
- Sorun giderme (500/419/asset 404)
- Yedekleme stratejisi

**Eklenen Dosyalar:**
- `app/Console/Commands/DbAktarMysql.php`
- `deploy-paketle.ps1`
- `.env.production.example`
- `docs/cpanel-deploy.md`

**Test:** `db:aktar-mysql` 30 tablo / 148 kayıt / 23 KB SQL üretti ✓

**Kullanım:**
```powershell
.\deploy-paketle.ps1
```
→ `deploy/cari-takip-deploy.zip` + `deploy/data.sql` + `deploy/KURULUM.md`

Sonra cPanel'e yüklenip File Manager'da extract edilir; KURULUM.md'deki adımlar takip edilir.

---

## Iter — Verileri Sıfırlama (yonet.php) (2026-07-04)

Kullanıcı isteği: "Kullanıcılar / Tanımlamalar / Para Birimleri / Firmalar / Sistem Ayarları hariç tüm verileri sil, tabloları sıfırla — temiz sayfa."

Kullanıcının terminal erişimi olmadığı için Artisan komutu yerine [public/yonet.php](../public/yonet.php) içine `veri-sifirla` case + kırmızı buton olarak eklendi. Buton `prompt('EVET')` ile onay ister.

**Yaklaşım:** Whitelist tabanlı TRUNCATE — AUTO_INCREMENT sıfırlanır (yeni kayıt ID=1'den başlar). FK checks geçici olarak kapatılır, işlem bitince açılır.

**Korunanlar:**
- Kullanıcılar/Yetki: `users`, `user_firma`, `user_firma_yetki`, `roller`, `yetkiler`, `sifre_gecmisi`
- Firmalar: `firmalar`, `firma_modulleri`, `firma_para_birimi`, `firma_banka_bilgileri`
- Para: `para_birimleri`, `kdv_oranlari`, `doviz_kurlari`
- Tanımlamalar (tüm `*_kategorileri / *_tipleri / *_statuleri / *_durumlari / *_turleri` + `ozel_alanlar`, `ozel_alan_degerleri`)
- Sistem: `sistem_ayarlari`, `ayarlar`, `yenileme_bildirim_sablonlari`
- Laravel altyapı: `migrations`, `cache*`, `jobs*`, `sessions`, `password_reset_tokens`, `push_subscriptions`

**Silinen:** cari, musteri, hareket, kasa, urun/stok, gorev_atananlar, proje, teklif, randevu, servis, yenileme, personel, belge, bildirim, loglar + tüm yorumlar.

**Kullanım:** `yonet.php` → 🔥 **Tüm Verileri Sıfırla** → prompt'a `EVET`.

Not: `sistem-yonetimi-temizle` mevcut case zaten benzer iş yapıyordu ama DELETE ile (AUTO_INCREMENT sıfırlanmaz) ve tanımlamaları da siliyordu. Yeni `veri-sifirla` case tanımlamaları KORUR + TRUNCATE ile ID'leri gerçekten sıfırlar.

---

## Iter — TestVeriSeeder ilişki alanları DB'den (2026-07-09)

Kullanıcı raporu: Test verileri üretildiğinde bazı ilişki alanları (kategori, tip, statü, tür, KDV) boş kalıyordu → filtreleme ekranlarında görünmüyordu.

**Yaklaşım:** `TestVeriSeeder`'a whitelist tabanlı `tanimlariYukle()` + `tanimId()` + `tanimAta()` helper'ları eklendi. `onkosullariHazirla`'da veritabanındaki 21 tanımlama tablosu (kategori/tip/statü/tür/durum/KDV/para birimi) önbelleğe alınır. Her modül fonksiyonunda ilgili FK alanları `Schema::hasColumn` ile safely doldurulur.

**Etkilenen tablolar/alanlar:**
- `cariler.kategori_id` ← `cari_kategorileri`
- `musteriler.kategori_id` ← `musteri_kategorileri`
- `kasalar.tip_id` ← `kasa_tipleri`
- `hareketler.kdv_orani_id` ← `kdv_oranlari`
- `urunler.kategori_id / birim_id / kdv_orani_id` ← `urun_kategorileri / urun_birimleri / kdv_oranlari`
- `projeler.tip_id / status_id / kdv_orani_id` — artık **her kayıt rastgele** (önce hep aynı ilk kayıt kullanılıyordu)
- `randevular.tip_id / statu_id` — her kayıt rastgele
- `teklif_satirlari.kdv_orani_id`
- `servis_satirlari.kdv_orani_id`

Kayıt oluşturma bozulmadı; şema uygun değilse alan sessizce atlanır. Filtre ekranlarında test verileri artık kategori/tip/statü seçilebilir.

**Bonus:** Aynı iterasyonda `yenileme_bildirim_sablonlari` seed'inde `aktif` sütununa `Schema::hasColumn` kontrolü eklendi (2026-06-27 migration'ıyla drop edildiği için hata veriyordu).

---

## Iter — Çoklu kullanıcı atama (2026-07-09b)

Kullanıcı raporu: Admin dışındaki kullanıcılar giriş yaptığında listeler boş — çünkü tüm test kayıtları hard-coded `$this->userId` (ilk aktif kullanıcı) atanıyordu.

**Değişim:** `TestVeriSeeder`'a `userIds` array + `rastgeleUser()` helper eklendi. `onkosullariHazirla`, `user_firma` pivot varsa firmadaki tüm kullanıcıları alır; yoksa tüm aktif kullanıcıları.

**Etkilenen alanlar:**
- `gorevler.atayan_id` → rastgele; `gorev_atananlar` pivot → 1-3 rastgele kullanıcı
- `randevular.atayan_id` → rastgele; `randevu_atananlar` pivot → 1-3 rastgele
- `teklifler.atanan_id / atayan_id` → rastgele
- `servisler.personel_id` → rastgele kullanıcı
- `yenileme_kayitlari.atayan_id` → rastgele
- `proje_yetkilileri` pivot → 1-3 rastgele kullanıcı

Log alanları (`hareketler.user_id`, `stok_hareketleri.user_id`, `mesai_kayitlari.user_id`) filtreye etki etmediği için değiştirilmedi.

---

## 2026-07-09 — Zorunlu Alan + Default İlk Seçim (Hareket / TeknikServis / Ürün / ParaBirimi)

**Amaç:** 4 modülde zorunlu Select alanlarına client-side validasyon + form açılışında default olarak listenin ilk elemanı seçili gelsin; bunun yanında Para Birimi modülüne yeni ekleme akışı doğrulanıp yetki + client validasyon tamamlansın.

**Değişim:**

- **Hareket `_FormModal.tsx`** — `useParaBirimleri()`'den `varsayilan` alındı. Yeni kayıt açıldığında `tur_id = turler[0].id`, `para_birimi = varsayilan`, `kur = kurAl(varsayilan)` otomatik set. `submit()` başında `tur_id` + `para_birimi` client-side kontrol; eksikte Toaster uyarı + kırmızı ring.
- **Hareket `_TopluModal.tsx`** — `bosSatir` imzasına `varsayilanTur` eklendi; TÜRler yüklenince ilk satırlar ve "Ekle" ile açılan yeni satırlar `tur_id`'yi `turler[0].id` ile gelir.
- **TeknikServis `_FormModal.tsx`** — Yeni servis açılışında `durum_id = durumlar[0].id` (Board önerisi öncelikli), `tur_id = turler[0].id`, `hizmet_turu_id = hizmet_turleri[0].id`. `submit()` başında üç alan için client-side kontrol. Select'lere hata ringi (`ring-1 ring-rose-500`).
- **Ürün `_FormModal.tsx`** — Yeni ürün için `kategori_id = kategoriler[0].id`. Submit'te client-side `kategori_id` kontrolü.
- **Para Birimi `Index.tsx`** — `useYetki()` importlandı; sağ üst `+` butonu `can('parabirimi.ekle')` ile korumalı (route zaten `yetki:parabirimi.ekle` middleware'i altında). `AyarModal.submit()`'e `kod / sembol / ad / sira` için client-side validasyon + input'lara `*` ve hata ringi.
- **`ParaBirimiController::validateStore`** — `sira` alanı `nullable` → `required` yapıldı (update rule değişmedi, mevcut kayıtlarda sıra zaten dolu).

**Not:** Backend `HareketController::validateHareket`, `TeknikServisController::validateServis` ve `UrunController::validateUrun` ilgili alanlar için zaten `required` içerdiği için backend değişikliğine gerek kalmadı. Para Birimi için ekleme rotası (`POST /para-birimleri`), controller `store`, model ve `parabirimi.ekle` yetkisi zaten mevcuttu; bu iterasyonda yalnızca `sira` zorunluluğu ve UX katmanı eklendi.

---

## 2026-08-10 — Yenileme Sistemi Dönüşümü (Faz A–D) + Ürün Senkronizasyon Onayı

**Kullanıcı isteği:** Yenileme kayıtlarındaki tüm parasal alanlar kaldırılacak; yerine Teklif Ürünleri sisteminin birebir aynısı ("Yenileme Ürünleri") kurulacak; değişiklik anasayfa/raporlar/bildirimler/yer tutucular dahil her yere yansıtılacak. Ek olarak TeklifUrun + ServisYedekParca + YenilemeUrun'da **yalnızca düzenlemede** ilişkili kayıtların da güncellenip güncellenmeyeceği sorulacak.

### Faz A — Yenileme Ürünleri kataloğu
`YenilemeUrun` modeli, controller (JSON `store`/`update`), migration `2026_07_10_000001_yenileme_urunleri` (kdv_orani_id baştan dahil), `Index.tsx` + `_FormModal.tsx`. Kayıt noktaları: `routes/web.php`, `Permissions.php` (`yenileme.urun`), `Moduller.php` sidebar, `AraController::yenilemeUrunleri` + `cozumle`, `AlanEtiketleri`, `AktiviteLogController`, `ModulModalProvider` (`yenileme-urun` modalı), `AsyncSelect.ENDPOINT_MODUL_MAP`.

### Faz B — Satır altyapısı
- Migration `2026_07_10_000002_yenileme_satirlari` — `teklif_satirlari`'nın birebir karşılığı (`kur` + `kdv_orani_id` baştan dahil, index `['yenileme_kayit_id','sira']`).
- `App\Models\YenilemeSatir` (TeklifSatir kopyası), `YenilemeKaydi::satirlar()` (`orderBy('sira')->orderBy('id')`), `YenilemeUrun::satirlar()`.
- `YenilemeKaydiController`: `formJson` satırları + `yenilemeUrunleri` katalogunu + `para_birimleri` alias'ını gönderir; `store`/`update` `satirlar` dizisini transaction içinde `satirlariKaydet()` ile yazar. Satır listesi validated diziden DEĞİL ham input'tan alınır (validated, satır `id`'sini eler ve id bazlı senkronizasyon bozulur — Teklif'te de aynı sebep).
- `_FormModal.tsx`: Teklif'in `SortableSatir` bölümü birebir port edildi (sürükle-sırala, akordiyon kapalı başlar, toplam özeti). Tek kasıtlı fark: kayıt seviyesi para birimi olmadığı için özet **varsayılan** para birimine çevrilir.

### Faz C — Fiyat alanlarının kaldırılması
- Migration `2026_07_10_000003_yenileme_fiyat_alanlari_kaldir` — `yenileme_kayitlari`'ndan `para_birimi`, `kur`, `kdv_orani_id`, `tutar` düşürülür. `hasTable`/`hasColumn` korumalı (idempotent). MySQL'de FK adı `information_schema`'dan bulunup raw `ALTER TABLE ... DROP FOREIGN KEY` + kalan index `DROP INDEX`; SQLite'ta `dropForeign` fallback. `down()` dördünü de geri ekler.
- `YenilemeKaydi`: fillable/casts/`kdvOrani()` temizlendi. Toplam hesabı **modele eklenmedi** — Teklif'te de model değil `TeklifController::ozet()` içinde; `YenilemeKaydiController::ozet()` birebir aynı hesabı `KurCevirici` ile yapar.
- Yer tutucular satır tabanlı `YenilemeBildirimSablonu::satirOzeti()` üzerinden üretilir: `{tutar}` `{ara_toplam}` `{indirimsiz_toplam}` `{kdv}` `{genel_toplam}` `{para_birimi}` + **yeni `{urunler}`**. Tüm çağrı noktalarında `satirlar.kdvOrani` eager-load (N+1 yok).
- `Public/YenilemeOnay.tsx`: müşteri onay sayfasına `TeklifGoster.tsx` referansıyla ürün satırı tablosu + toplam özeti.
- Dashboard/Board/Tablo tutar gösterimleri satır toplamına çevrildi; Board/Tablo para birimi filtresi hardcoded `[TRY,USD,EUR]` yerine controller'dan gelen aktif ParaBirimi listesinden beslenir.

### Faz D — "İlişkili kayıtlar da güncellensin mi?"
- **Yeni:** `app/Support/UrunSatirSenkron.php` trait — `iliskiliSatirlariEsitle()` + `iliskiliSatirSayisi()`. `getChanges()` ile **sadece o düzenlemede gerçekten değişen** alanlar tek toplu `UPDATE` ile satırlara yazılır (N+1 yok); dokunulmayan alan satırda olduğu gibi kalır, böylece kullanıcının satır üzerindeki el düzenlemeleri boş yere ezilmez.
- Alan haritaları: TeklifUrun→`teklif_satirlari.teklif_urun_id` (`ad→urun_adi`), YenilemeUrun→`yenileme_satirlari.yenileme_urun_id` (`ad→urun_adi`), ServisYedekParca→`servis_satirlari.yedek_parca_id` (`ad→parca_adi`). Üçünde de `para_birimi`/`kdv_orani_id`/`birim_fiyat`/`indirimsiz_birim_fiyat`/`aciklama` ortak.
- Üç `update()` de `DB::transaction` içinde: ürün her hâlükârda kaydedilir, `iliskileri_guncelle` true ise senkronizasyon yapılır ve başarı mesajına etkilenen satır sayısı eklenir.
- Frontend: `ConfirmDialog`'a `useConfirmIkili()` eklendi (`'onay' | 'ikincil' | null`; ESC/overlay = `null`). Üç `_FormModal` düzenleme modunda, **Kaydet / Kaydet ve Yeni / Kaydet ve Ekle** akışlarının hepsinde soruyu sorar. Vazgeç hiçbir şey kaydetmez, modal açık kalır.

### Aynı turda kapatılan kural ihlalleri
- **`useSilVeyaPasif` "Pasife Al" butonunu hiç çizmiyordu.** `bagliVeri`/`pasifMetni`/`pasifEdilebilir`/`silEdilebilir` tanımlıydı ama render'da kullanılmıyordu; 28 çağrı yeri doğru parametreleri gönderiyor, `mod=${secim}` backend'e gidiyor olmasına rağmen kullanıcı hiçbir zaman pasife alamıyordu. Dialog artık İptal + **Tamamen Sil** (kenarlıklı rose) + **Pasife Al** (dolu amber, autoFocus/varsayılan) çiziyor, `bagliVeri` cascade uyarısı kırmızı kutuda görünüyor.
- **TeklifUrun toplu silme:** UI `/teklif-urunleri/toplu-sil`'e POST ediyordu ama route + `topluSil()` yoktu; üstelik `teklif-urunleri/{urun}` route'u URL'i yakalayıp 404 veriyordu. Backend eklendi, route `{urun}`'den ÖNCE tanımlandı. `destroy()` de `mod`'u yok sayıp her şeyi pasife alıyordu, düzeltildi.
- **ServisYedekParca:** `destroy()` `mod` desteklemiyordu (hep pasif), `topluSil()` + route + liste UI'ı hiç yoktu. Üçü de eklendi (toplu seçim + yüzen çubuk + `useSilVeyaPasif` onayı).
- **TestVeriSeeder:** hâlâ kaldırılmış 4 sütuna insert ediyordu → "Test Verisi Üret" patlayacaktı. Temizlendi; `yenilemeUrunleri()` + `yenileme_satirlari` üretimi (kayıt başına 1-4 satır, gerçek FK'ler) eklendi. **Ayrıca bulunan bug:** tür atama bloğu `$adet` döngü değişkeninin üzerine yazıyordu, yenileme kayıtları 100 değil 1-3 üretiliyordu — `$turAdedi` olarak ayrıldı. Eksik `yorumDoldur('yenileme_yorumlari', 'kayit_id', ...)` çağrısı eklendi.
- **`yonet.php`:** test verisi önizleme/temizleme/sayım listelerine `yenileme_urunleri` + `yenileme_satirlari` eklendi (FK sırasına uygun). Veri sıfırlama whitelist'inden `yenileme_kayit_turleri` **çıkarıldı** — tanımlama tablosu değil, `yenileme_kayitlari ↔ yenileme_turleri` pivotu; korunduğu için kayıtlar silinirken yetim pivot satırları kalıyordu.

### Deploy (sunucuda terminal yok)
`yonet.php` → **Veritabanını Güncelle** (3 migration) → sonra istenirse **Test Verisi Üret**. Bu sıra önemli: tablolar yokken test verisi üretilirse satırlar sessizce atlanır (hasTable koruması) ve yenileme toplamları 0,00 görünür.

---

## 2026-08-11 — Test Verisi: Tüm Firmalar + Eksiksiz Kapsam

**Kullanıcı isteği:** Korunan liste (kullanıcı/yetki, firmalar, para & KDV, tanımlamalar, sistem, Laravel altyapı) dışındaki **tüm** alanlara eksiksiz derinlemesine test verisi; **tüm firmalar için ayrı ayrı**.

### Ana değişim — firma döngüsü
`TestVeriSeeder` daha önce yalnızca **ilk aktif firma** için üretiyordu (`Firma::where('aktif',true)->first()`). Artık `run()` tüm aktif firmaları dolaşıyor. Dosya 1.200 → 2.856 satır.

- **Firma başına transaction:** bir firma patlarsa yalnızca o firmanın verisi geri alınır, diğerleri üretilmeye devam eder; hata rapora yazılır.
- **`firmaBaglamiKur()`** her iterasyonda 11 property'yi sıfırlar (`firmaId`, `firmaAd`, `userIds`, `userId`, `tanimlar`, `paraBirimleri`, `paraBirimleriKisa`, `varsayilanPb`, `sayim`, `sonFirmaUyarilari`, `kodSuffix`). Tanımlama önbelleği firmalar arası sızmaz — bir firmanın kategorisi diğerinin kaydına atanamaz.
- **Kod çakışması:** `kodSuffix = 'F{firmaId}-{tabanSuffix}'` — aynı saniyede iki firma aynı kodu üretemez. Kolon uzunlukları doğrulandı (cari_kodu 30, projeler.kod 30, yenileme kod 30).
- **Kullanıcı dağıtımı:** her firma kendi kullanıcılarını `user_firma` pivotundan çeker; `user_id` gibi sabit alanlar bile o firmanın kullanıcılarından seçilir (admin'e yığılmaz). Pivot kaydı yoksa tüm aktif kullanıcılara düşer **ve uyarı üretilir** — sessiz fallback yok.
- **Tanımlama eksikliği:** firmanın gerekli tanımlaması yoksa o modül atlanır, patlamaz, uyarı olarak raporlanır.
- Çeşitlilik artırıldı: durum/statü dağılımı, aktif+**pasif** kayıt (Pasifleri Göster filtresi test edilebilir), geçmiş/bugün/gelecek tarih dağılımı, çoklu para birimi.

### Eklenen 8 tablo
Daha önce hiç veri almayan, korunan listesinde de olmayan tablolar: `aktivite_loglari`, `belgeler`, `para_birimi_yorumlar`, `proje_otomatik_gorevler`, `sistem_loglari`, `teklif_yanitlari`, `urun_yorumlari`, `yenileme_loglari`. Log tabloları gerçek modül kodları + gerçek kayıt id'leriyle dolduruldu (uydurma id ekranda kırık satır gösterirdi). Sekizi de `temizle()` kapsamına FK sırasıyla eklendi.

### Korunan tablo ihlali giderildi
Seeder eskiden `yenileme_bildirim_sablonlari`'na yazıyordu — bu tablo kullanıcının korunan listesinde. Üretim kaldırıldı; ayrıca **geriye dönük temizlik** eklendi: eski çalıştırmalardan kalan `[TEST_VERI]` etiketli şablon kalıntıları siliniyor.

### `yonet.php`
Önizleme artık firma sayısını ve firma×100 toplam tahminini gösteriyor. Sayım/temizlik/sistem-temizleme listelerine 8 yeni tablo FK sırasıyla eklendi. `set_time_limit(0)` + 512M bellek.

**Veri Sıfırla whitelist'i** kullanıcının korunan listesiyle eşitlendi. İki bilinçli çıkarma, ikisi de yorum satırıyla gerekçelendirildi:
- `yenileme_kayit_turleri` — tanımlama değil, `yenileme_kayitlari ↔ yenileme_turleri` **pivotu**; korunursa kayıtlar silinirken yetim pivot satırı kalıyordu.
- `ozel_alan_degerleri` — tanımlama değil, **kayıt bazlı** değer deposu (`entity_type` + `entity_id`). Parent'lar TRUNCATE edilip AUTO_INCREMENT 1'e döndüğü için korunursa eski değerler yeni kayıtların üstüne biniyordu (silinmiş cari #1'in değerleri yeni cari #1'de görünürdü). Alan **tanımları** (`ozel_alanlar`) korunduğu için ekranlar aynen çalışır. Geri alınmak istenirse whitelist'e tek satır eklemek yeterli.

Ayrıca **whitelist öz denetimi** eklendi: tanımlama adlandırmasına uyan ama whitelist'te olmayan bir tablo varsa (yeni tanımlama tablosu eklenip whitelist güncellenmezse) sessizce silinmesin diye sesli uyarı verilir.

### Deploy
`yonet.php` → **Veritabanını Güncelle** → **Test Verisi Üret**. Bu sıra önemli: tablolar yokken üretilirse `hasTable` koruması satırları sessizce atlar.

---

## 2026-08-11 — Müşteri Sözleşmeleri Modülü (CRM)

**Kullanıcı isteği:** CRM grubuna "Müşteri Sözleşmeleri" bölümü. Şablon oluştur, müşteriye onaylat, imza al. **En kritik kural:** şablon içeriği değişse bile önceden onaylanmış sözleşmeler asla bozulmayacak.

### Veri modeli — snapshot mimarisi
- `sozlesmeler` (ŞABLON): kod (sistem üretir, düzenlenemez), ad, icerik (HTML), aktif.
- `musteri_sozlesmeleri` (SNAPSHOT): onay anında şablondan **kopyalanan** sozlesme_kod / sozlesme_ad / icerik (yer tutucular doldurulmuş) + imza_ad_soyad + imza (base64 PNG) + onay_tarihi + onaylayan_id + ip_adresi + aktif.
- `sozlesme_id` yalnızca izlenebilirlik için, **nullOnDelete** — şablon tamamen silinse bile snapshot ayakta kalır.
- Kullanım sayısı: `withCount('musteriSozlesmeleri as kullanim_sayisi')`.

### Ekranlar
- `Sozlesme/Index` — kullanım sütunu + filtre sheet (ad/kod/metin içi arama/kullanım aralığı/tarih/pasif) + toplu silme + pagination.
- `Sozlesme/_FormModal` — %25 Bilgiler, %50 Detay (RichTextEditor), %25 Yer Doldurucular; mobilde sekmeli. Kod salt okunur. Yer tutucular gruplu+aramalı, tıklayınca `imlecAEkle` ile editörde imlecin olduğu yere eklenir. Kullanımdaki şablon düzenlenirken "onaylanmış sözleşmeler etkilenmez" uyarısı çıkar.
- `MusteriSozlesme/Index` — müşterinin onaylı sözleşmeleri; Müşteriler listesindeki FileSignature ikonundan açılır.
- `MusteriSozlesme/_OnayModal` — 2 adım: (1) AsyncSelect ile şablon seç, (2) yer tutucuları DOLDURULMUŞ metin okunur olarak gösterilir (editör değil) + SignaturePad + Ad Soyad + "Onaylıyorum". Ad Soyad ve imza zorunlu.
- `MusteriSozlesme/Goster` — snapshot metin + imza görseli + onay tarihi/IP; `@media print` ile yalnızca belge yazdırılır.

### RichTextEditor genişletildi
Tam ekran, tablo, metin/arka plan rengi, hizalama, HTML kaynak modu, geri/ileri, biçim temizleme + imperative API (`imlecAEkle`, `imlecAHtmlEkle`, `odakla`, `htmlAl`, `temizle`, `tamEkranAyarla`). `sanitizeZengin` ile hem sunucuda hem istemcide XSS koruması.

### Zorunlu entegrasyonlar (kullanıcı: "sakın atlama")
routes (toplu-sil, {model} route'undan ÖNCE) · `musteri_sozlesme` yetkileri (goruntule/ekle/duzenle/sil/toplu_sil) · Moduller.php **CRM grubu içinde** sidebar + "+" hızlı ekleme · `modul:musteri` middleware · dashboard widget (backend+frontend) · `/api/ara/sozlesmeler` + cozumle · AlanEtiketleri · AktiviteLog · ModulModalProvider mount · AsyncSelect eşlemesi · AppLayout/GenelArama ikon map · yonet.php listeleri.

### Doğrulama 13 bulgu çıkardı — hepsi onarıldı
Paralel ajanlar birbirinin isimlendirmesini bilmediği için ciddi hatalar oluşmuştu:

1-4. Route'lar **ters controller'a** bağlıydı; olmayan `MusteriSozlesmeKayitController` sınıfı ve `alanGuncelle` metodu çağrılıyordu; route parametreleri controller imzalarıyla uyuşmadığı için implicit binding kırıktı (her istek 403/500). Route bloğu gerçek imzalara göre yeniden yazıldı.
5. **Snapshot'a sozlesme_kod / sozlesme_ad / imza_ad_soyad / onay_tarihi yer tutucuları BOŞ yazılıyordu** — `icerikUret()` ek parametre almıyordu. Snapshot olduğu için sonradan düzeltilemezdi. Artık hem önizleme hem kayıt aynı ek değerlerle üretiliyor; kullanıcının gördüğü metin ile kaydedilen birebir aynı.
6. `musteri_sozlesmeleri.aktif` kolonu yoktu, "Pasife Al" kalıcı olarak ölüydü. Migration + model fillable/cast eklendi.
8-9. Dashboard verisi gönderiliyor ama çizilmiyordu; `FileSignature` ikonu IKON_MAP'lerde kayıtlı değildi.
10. `urlToModul` `/sozlesmeler` arıyordu, gerçek URL `/musteri-sozlesmeleri`.
11. AsyncSelect eşlemesi açıktı ama modal mount edilmemişti (ölü "+" butonu); Parça 2'de mount edilince açıldı.

### Deploy
`yonet.php` > **Veritabanını Güncelle** (migration `2026_08_11_000001_sozlesme_modulu`). Yetkiler kullanıcı x firma bazlı olduğu için ilgili kullanıcılara `musteri_sozlesme.*` izinleri verilmelidir.

---

## Sıradaki Adımlar

1. **Iter-13 (2FA TOTP)** — Google Authenticator entegrasyonu.
2. **Iter-14 (Dashboard grafikler)** — Recharts.

## 2026-08-15 — Kırık görsellerin gerçek sebebi: mutlak URL + şema uyumsuzluğu

Görseller "hâlâ görünmüyor" turunda tahmin etmeyi bırakıp isteği gerçek HTTP
çekirdeğinden geçirdim. Bulgular ve düzeltmeler:

**1. `Belge::getUrlAttribute()` mutlak URL üretiyordu** *(asıl sebep)*
`route('belgeler.preview', $id)` şemayı isteğin kendisinden çıkarır. Uygulama
TLS'i sonlandıran bir proxy'nin arkasındaysa Laravel isteği `http` sanar ve
frontend'e `<img src="http://...">` gider. Site HTTPS olduğu için tarayıcı bunu
**karışık içerik** sayıp engeller: sayfalar normal açılır, yalnızca görseller
kırılır — kullanıcının bildirdiği tablo tam olarak budur.
→ `absolute: false` ile **göreceli** URL (`/belgeler/7/onizleme`). Hem şema hem
host kaynaklı kırılma sınıfını birlikte kapatır. `->url` yalnızca iki yerde,
ikisi de frontend'e `<img src>`/`<a href>` olarak gidiyor; mutlak gerektiren
(mail/PDF) kullanım yok, bu yüzden göreceli tamamen güvenli.

**2. `Belge::getBoyutAttribute()` sabit yol varsayıyordu**
`storage_path('app/public/'.$yol)` — public diskin kökü farklıysa dosya var
olduğu hâlde `0 byte` döner. Diskin kendisine soruldu (`Storage::disk('public')`),
`BelgeController::fizikselYol()` ile aynı desen.

**3. `SonaSlashEkle` dosya servis eden uçları da yönlendiriyordu**
`/belgeler/{id}/onizleme` → 302 → `.../onizleme/`. Bunlar gezilebilir sayfa
değil, dosya uçları; kanonikleştirmeden kazançları yok, her görselde fazladan
istek doğuruyor ve sunucuda ters yönlü bir rewrite varsa döngü riski taşıyor.
`belgeler` muaf listesine eklendi.

**4. `yonet.php > Görsel/Dosya Tanı`'ya şema teşhisi eklendi**
Tarayıcı şeması, `X-Forwarded-Proto`, Laravel'in ne sandığı, `APP_URL` ve
üretilen mutlak URL yan yana yazılıyor; uyumsuzlukta uyarı çıkıyor. Sunucudan
tahminsiz ölçüm alabilmek için.

**Doğrulama:** gerçek middleware zinciriyle üç uç da `HTTP 200 image/png`
(`/onizleme`, `/onizleme/`, `/indir`) — daha önce 302→500 idi.

**Not:** `TrustProxies` yapılandırılmamış. Görselleri artık etkilemiyor (göreceli
URL), ama yönlendirme/sayfalama bağlantıları http üretebilir. Tanı ekranı
uyumsuzluk gösterirse ele alınacak.

### Düzeltme — hipotez çürüdü, asıl engel temizleyicideydi

Sunucudan alınan tanı çıktısı iki şeyi kanıtladı:

* **Şema tutarlı** (`https` / `https`, `APP_URL` doğru) → karışık içerik hipotezi
  **yanlıştı**. Göreceli URL yine de doğru bir sağlamlaştırma, geri alınmadı.
* **`TOPLAM kayıt: 201`** → temizleme butonu hiçbir şey silmemiş. Kırık görsellerin
  sebebi baştan beri tek şey: dosyası olmayan ~174 hayalet `belgeler` satırı.

Yapılanlar:

1. **`belge-eksik-temizle` kendi kendini kanıtlar hâle getirildi.** Artık istek
   yöntemini, gelen POST anahtarlarını ve `onayla` kutusunun ulaşıp ulaşmadığını
   yazıyor; silme sonrası toplam kaydı yeniden okuyup beklenenle karşılaştırıyor.
   "Onayladım ama silinmedi" durumu artık tahmin değil, ölçüm.
2. **`whereIn` 500'lük dilimlere bölündü.** Binlerce id'lik tek sorgu bazı MySQL
   yapılandırmalarında `max_allowed_packet`'a takılıp sessizce 0 satır siler.
3. **Eksik dosya artık kırık görsel olarak çıkmıyor.** `DosyaYoneticiController`
   satır başına `mevcut` bayrağı gönderiyor (sayfa başına 40 stat, ihmal
   edilebilir); tablo ve kutu görünümü `<img>` denemek yerine kırmızı kesikli
   çerçeveli "Dosya bulunamadı" uyarısı basıyor. Bu, kayıtlar temizlenmese bile
   kullanıcının sebebi görmesini sağlar.

### Dosya Yöneticisi — önizleme kaldırıldı, tıklama ilişkili kaydı açıyor

İstenen: önizleme yok; **indir**, **tıklayınca ilişkili kaydın modal'ı**, **sil**,
checkbox kalsın.

* **Önizleme bağlantıları kaldırıldı.** Hem tablo görünümündeki dosya adı hem kutu
  görünümündeki küçük resim `<a href={url} target="_blank">` idi (dosyayı yeni
  sekmede açıyordu); ikisi de `<button>`'a çevrildi. Küçük resim GÖRSEL olarak
  duruyor — kaldırılan, tıklayınca dosyayı açma davranışı.
* **Tıklama → ilişkili kaydın modal'ı.** Yeni modal YAZILMADI; `app.tsx`'te kök
  seviyede zaten mount edilmiş sağlayıcılar kullanıldı: Görev için
  `useGorevModal().gorevDuzenle(id)`, diğer 10 tip için
  `useModulModal().ac(modul, id)`. Modül adı `urlToModul(iliski.yol)` ile çözülür.
  Eşleme bulunamazsa ilgili listeye gidilir — tıklama sessizce ölmez.
* **`urlToModul`'da gerçek boşluk kapatıldı:** `proje` geçerli bir `ModulAdi` ve
  `ProjeFormModal` sağlayıcıda mount edilmiş olmasına rağmen `/projeler` eşlemesi
  yoktu, `null` dönüyordu. Eklendi (Dosya Yöneticisi dışındaki çağıranlara da yarar).
* **İndir / Sil / checkbox** zaten mevcuttu, olduğu gibi korundu.

### Sonda "/" kuralı — frontend süpürmesi + yönlendirme cevapları

Kural ihlali bildirildi. Üç cephede ele alındı:

**1. Geçen turdaki ihlal geri alındı.** `SonaSlashEkle::HARIC_ONEKLER`'e eklediğim
`belgeler` muafiyeti kaldırıldı. Gerekçesi zaten çürümüştü (şema tutarlı çıkmıştı,
500 lokal APP_KEY artefaktıydı).

**2. Frontend: 273 adres, 97 dosya.** `href`/`action`, `router.visit|get|post|put|
patch|delete` ve `fetch` bağlamlarındaki adreslere sonda "/" eklendi. Atlananlar:
`storage|build|up|vendor|api|livewire|sanctum` önekleri (dosya servis eder, slash
bozar) ve uzantılı adresler. Sorgu dizesi varsa slash "?"in ÖNÜNE girer
(`/gorevler/?gorev=5`). Build + tsc temiz, çift slash üretilmedi.
`Belge::url` ve yeni `Belge::indir_url` kanonik biçimi doğrudan üretir; frontend
artık indirme adresini elle kurmuyor.

**3. Sunucu tarafı: `redirect()->route()`.** Önce `URL::formatPathUsing()` denendi —
**etkisiz**: Laravel'in `UrlGenerator::format()` metodu en sonda
`trim($root.$path, '/')` yapıp eklenen slash'ı geri kırpıyor. Çekirdeği ezmek
yerine kural, `SonaSlashEkle`'nin çıkışında **Location başlığına** uygulandı
(`konumuDuzelt`). Böylece form gönderimi sonrası gereksiz ikinci tur da kalktı ve
kural tek bir sınıfta toplandı — istek yönü ve cevap yönü aynı `HARIC_ONEKLER`
listesini paylaşıyor (sabit `private` → `public` yapıldı).

**Doğrulama:**
* Location: düz/slash'lı/sorgulu/storage/build/kök/imzalı — 8 durum tek tek test
  edildi, hepsi beklendiği gibi.
* **İmzalı URL güvenliği:** şifre sıfırlama bağlantısına slash eklenince imza
  bozulur mu diye ölçüldü — `Request::url()` sondaki slash'ı kırptığı için imza
  **her iki biçimde de GEÇERLİ**. Şifre sıfırlama / e-posta doğrulama etkilenmiyor.
* `route:list` 415 rota, hata yok.

### Arka plan yükleme merkezi — takılan yüklemeler ve global gösterge

Kullanıcı: yüklemeler takılıyor; iptal edince spinner durmuyor; "arka planda devam
et" diyince devam eden yüklemeler her sayfada sağ altta sabit bir ikonda görünsün,
tıklayınca modal açılsın.

**Bulunan hatalar (üçü de gerçek, üçü de aynı kök nedene bağlı: yükleme sahipliği
sayfadaydı):**

1. **"Arka planda devam et" tam tersini yapıyordu.** Buton `kapat`'a bağlıydı;
   `kapat` devam eden tüm XHR'leri `abort()` ediyordu. Yani arka planda devam
   ettirmek yerine iptal ediyordu.
2. **İptal spinner'ı sonsuza kadar döndürüyordu.** `abort()` bir `setKuyruk`
   güncelleyicisinin İÇİNDE çağrılıyordu. `abort()` senkron olarak `onabort`'u
   tetikliyor, o da aynı güncelleyicinin içinden ikinci bir `setState` yapıyordu;
   iç içe güncelleme kayboluyor, iş `yukleniyor` durumunda kalıyordu. Aynı hata
   `FileManager.yuklemeIptal`'de de vardı.
3. **Hepsi aynı anda gönderiliyordu.** 10 dosya = 10 paralel POST. Tarayıcı host
   başına ~6 bağlantıda tıkanıyor. Üstüne `BelgeController::store` **`back()`**
   yani 302 dönüyordu: XHR yönlendirmeyi takip edip her dosya için referer
   sayfasını baştan çekiyordu. Ayrıca istemci 2xx-3xx'i başarı saydığı için
   **reddedilen dosyalar "Yüklendi" görünüyordu** (sessiz güvenlik hatası).

**Çözüm:**

* **`YuklemeProvider`** (yeni) — yükleme sahipliği app.tsx'te kök seviyeye taşındı.
  XHR'ler **ref'te** tutulur, iptal her zaman state akışının dışında yapılır.
  Aynı anda en fazla **3** iş koşar, gerisi kuyrukta bekler. Inertia gezinmesi
  yüklemeyi kesmez. Sekme kapatılırken devam eden yükleme varsa uyarır.
* **`YuklemeGostergesi`** (yeni) — her sayfada sağ altta sabit, ilerleme halkalı
  yuvarlak buton (iş yokken hiç çizilmez). Tıklayınca dosya bazında yüzde, hata
  mesajı ve iptal içeren modal açılır; "Bitenleri Temizle" ile liste sadeleşir.
* **`_YukleModal`** artık yalnızca dosya seçme ekranı; yüklemeyi merkeze devreder.
* **`FileManager`** de merkeze bağlandı; kendi işlerini `hedefKey` ile süzüp
  ilerlemeyi yine yerinde gösterir. Tamamlanınca `${hedefKey}:tamam` olayıyla
  listesini tazeler.
* **`BelgeController::store`** JSON bekleyen isteklere **JSON** döner; reddetmeler
  **422 + mesaj**. Yönlendirme takibi ve sessiz "başarılı" hatası ortadan kalktı.
* `_YukleModal`'ın POST adresi `/dosya-yoneticisi` idi — sonda "/" kuralına
  uyduruldu (`xhr.open` bağlamı önceki süpürmenin dışında kalmıştı).

Typecheck ve build temiz.

### Yükleme alanı düzeltmeleri + 1 GB sınırı

1. **"Yüklendi / İptal edildi" satırları asılı kalıyordu.** `FileManager`'ın satır
   içi kuyruğu artık yalnızca *yükleniyor / sırada / hata* durumlarını gösterir.
   Biten dosya zaten aşağıdaki listeye düşüyor; "Yüklendi" satırının yükleme
   alanının üstünde durması gereksiz gürültüydü. **Hatalar kalır** — kullanıcı
   dosyanın neden reddedildiğini görmeli. Genel göstergede de biten/iptal edilen
   işler 4 sn sonra kendiliğinden düşer (hatalar yine kalır).

2. **İptalde boşuna tazeleme.** Tamamlanma olayı artık `detail.basarili` taşıyor;
   dinleyen taraf (FileManager ve Dosya Yöneticisi) hiçbir dosya yüklenmediyse
   listeyi tazelemiyor. Öncesinde tek dosya iptal edilse bile tam liste isteği
   gidiyordu.

3. **Genişlik dosya adına göre değişiyordu.** `FileManager` kök `<div>`'inde
   genişlik kısıtı yoktu. Flex/grid içindeki bir kutunun asgari genişliği
   içeriğinin doğal genişliği kadardır; uzun dosya adı `truncate`'e rağmen kabı
   zorluyordu. Köke `w-full min-w-0 overflow-hidden` eklendi.

4. **1 GB yükleme.** Sunucu doğrulaması `max:20480` → `max:1048576` (KB), her iki
   kontrolcüde. Arayüz metinleri "1 GB" oldu.
   PHP tavanı için `public/.user.ini` eklendi (upload 1024M / post 1064M /
   execution+input 1800 sn). **`.htaccess` içine `php_value` YAZILMADI**: mod_php
   yoksa Apache "Invalid command" verip SİTEYİ 500'e düşürür; `.user.ini` ise
   FPM/CGI'de okunur, desteklenmiyorsa sessizce yok sayılır.
   `yonet.php > 📏 Yükleme Limitleri` sunucudaki gerçek değerleri gösterir ve
   1 GB için yeterli olup olmadığını söyler (PHP bu dosyayı ~5 dk önbellekler).

Typecheck ve build temiz.

### Düzeltme — provider ağacı + genişlik (asıl sebep modal sütunlarındaydı)

**1. "useYukleme() <YuklemeProvider> içinde kullanılmalı" hatası.**
`YuklemeProvider`'ı ağacın EN İÇİNE koymuştum. Modal sağlayıcıları (ModulModal /
GorevModal / ProjeModal) kendi modallarını `{children}`'ın **kardeşi** olarak
mount eder; dolayısıyla Cari/Hareket/Müşteri/... form modalları YuklemeProvider'ın
DIŞINDA kalıyordu ve içlerindeki `FileManager` `useYukleme()` çağırınca modal hiç
açılmıyordu. Sağlayıcı tüm modal sağlayıcılarının DIŞINA alındı; ters bağımlılık
yok (YuklemeProvider hiçbir modal context'ini kullanmaz). Modal sağlayıcılarının
kendi aralarındaki sıra korundu.

**2. Genişlik dosya adına göre değişiyordu — önceki düzeltme yetersizdi.**
`FileManager` köküne `w-full min-w-0` eklemek yetmedi, çünkü büyüyen kap
bileşenin kendisi değil onu saran MODAL SÜTUNUYDU: `flex min-h-0 flex-1
md:basis-[25%]`. Flex öğesinin varsayılan `min-width: auto` değeri, öğenin
içeriğinin doğal genişliğinin altına inmesini ENGELLER; bu yüzden uzun dosya adı
`truncate`'e rağmen sütunu `basis`'in ötesine itiyordu.
`min-h-0 flex-1` geçen **70 sütuna** `min-w-0` eklendi (11 modal). Sadece dosya
sütunu değil, aynı satırdaki tüm sütunlar — biri küçülemezse komşularını iter.
Teklif ve YenilemeKaydi modalleri bu düzeltmeye zaten kısmen sahipti; desen
şimdi tüm modallerde aynı.

### Dosya Yöneticisi — sürükle-bırak liste alanına, "+" doğrudan seçici

* **Yükleme modalı kaldırıldı.** `_YukleModal.tsx` silindi (başka kullanıcısı yoktu).
  `+` butonu artık gizli `<input type="file" multiple>` öğesini tetikliyor, ara
  pencere yok.
* **Liste alanı bırakma bölgesi oldu.** Dosya sürüklenince alan mavi kesikli
  çerçeveli bir kaplamaya döner. İki incelik:
  - `dragenter/dragleave` alt elemanlarda da tetiklenir; tek boolean ile izlenirse
    imleç satırlar üzerinde gezerken kaplama titrer. **Derinlik sayacı** kullanıldı,
    yalnızca alandan gerçekten çıkılınca sıfırlanıyor.
  - Kaplama `pointer-events-none`; aksi hâlde kendisi `dragleave` üretip
    kendi kendini kapatır.
  - Yalnızca `dataTransfer.types` içinde `Files` varsa tepki verilir (metin/eleman
    sürüklemesinde kaplama açılmaz).
* Yükleme yine arka plan merkezine devrediliyor; tamamlanma olayı ve "hiçbir dosya
  yüklenmediyse tazeleme" davranışı sayfaya taşındı.
* **Bilgilendirme modalı güncellendi** (`modulBilgi.ts` → `dosya_yonetici`):
  yeni "Dosya yükleme" bölümü (sürükle-bırak, `+` davranışı, 10 dosya / 1 GB,
  arka plan yükleme, sağ alt gösterge, 3'lü eşzamanlılık), "Nasıl kullanılır"
  bölümüne tıklama→kayıt penceresi ve "Dosya bulunamadı" işareti eklendi,
  uyarılara hata satırı davranışı ve sunucu limiti notu yazıldı.
  NOT: Bundan sonra her modül değişikliğinde bilgilendirme modalı da güncellenecek
  (kullanıcı kesin zorunluluk olarak belirtti).

### Silme sonrası çift bildirim

Tek bir dosya silmede birden fazla "silindi" mesajı çıkıyordu. Sebep **iki ayrı
kaynak**tı:

1. Sunucu: `destroy()` / `topluSil()` → `back()->with('success', 'Dosya silindi.')`
   → `ToasterContainer` flash'ı bildirime çeviriyor.
2. İstemci: `router.delete(..., { onSuccess: () => Toaster.basari('Dosya silindi') })`

**Düzeltme (Dosya Yöneticisi):** istemci tarafındaki elle toast kaldırıldı. Sunucu
mesajı hem garanti hem daha doğru — toplu silmede gerçek adedi içeriyor
("3 dosya silindi."). `topluSil`'in `onSuccess`'i yalnızca seçimi temizliyor.

**Emniyet supabı (genel):** aynı desen kod tabanında **69 yerde** var; hepsini tek
tek denetlemek, sunucusu flash basmayan bir uç varsa bildirimi tamamen yok etme
riski taşıyor. Bu yüzden `ToasterContainer.ekle()` içine dar pencereli tekrar
koruması eklendi: aynı tip + normalleştirilmiş metin (sondaki noktalama/boşluk
atılır, Türkçe küçük harfe çevrilir) **800 ms** içinde ikinci kez gelirse
gösterilmez. Pencere kasıtlı olarak dar — çift bildirim aynı yanıt döngüsünde
~50 ms içinde oluşur; kullanıcının onay penceresinden geçip aynı işlemi bu sürede
tekrarlaması mümkün değil, yani meşru tekrarlar bastırılmıyor.

Bilgilendirme modalı: `dosya_yonetici` metni bildirim davranışını anlatmıyor,
kullanıcıya görünen bir akış/sınır değişmedi — güncelleme gerekmedi.

### Para Birimleri — ekleme artık düzenlemeyle AYNI form

Ekleme modalı hâlâ 4 alanlık (kod/sembol/ad/sıra) sade formdu; varsayılan işareti,
TCMB kodu, kur tipi, kâr marjı ve otomatik güncelleme yalnızca kayıt oluştuktan
SONRA girilebiliyordu.

Kök neden: `yeniEkle` → `modulModal.ac('parabirimi','yeni')` çağırıyor, sağlayıcı
da `_FormModal`'ı (sade form) mount ediyordu. Oysa `AyarModal` (düzenleme modalı)
`pb === 'yeni'` modunu **zaten destekliyordu** ve o modda Notlar'ı gizliyordu —
istenen form mevcuttu, sadece ekleme yolu ona bağlı değildi.

* `yeniEkle` → `setDuzenle('yeni')`: ekleme ve düzenleme tek form. Sidebar'ın
  `?yeni=1` bağlantısı da aynı yolu kullandığı için o giriş de düzeldi.
* **"Kaydet ve Yeni"** eskiden `ac('parabirimi','yeni')` ile üstte sade modalı
  açıyordu (yani ikinci kayıt yine eksik formla giriliyordu). Artık ekleme
  modunda form yerinde sıfırlanıyor, düzenleme modunda sayfa `onYeniDevam` ile
  modalı ekleme moduna alıyor. Başlangıç değerleri tek kaynakta: `BOS_PARA_BIRIMI`.
* **TCMB canlı listesi "+"** de sade modalı açıyordu. Artık ekleme modundaysa
  AÇIK OLAN formu dolduruyor (üste ikinci modal açmak gereksizdi); düzenleme
  modundaysa seçimi saklayıp ekleme moduna geçiriyor, form açılışta okuyor.
* `_FormModal.tsx` içeriği yönlendirmeye indirildi. Artık onu çağıran yok, ama
  sağlayıcıda `parabirimi` anahtarı kayıtlı; dosyayı silmek yerine `/para-birimleri/?yeni=1`
  yönlendirmesi bırakıldı ki ileride bir yol oraya düşerse kullanıcı eski/eksik
  formu değil doğru sayfayı görsün.
* Ölü kalan `useModulModal` tanımları (sayfa + AyarModal) kaldırıldı.

**Bilgilendirme modalı güncellendi** (bu sayfanınki `modulBilgi.ts`'de değil,
Index içinde gömülü): ekleme-düzenleme aynı form ve Notlar'ın neden gizli olduğu,
TCMB listesinden forma dolgu, "Kaydet ve Yeni" davranışı yazıldı.

### Para Birimi ekleme modalında sağdaki boşluk

Modal üç sütun: Detay + Notlar (%25) + TCMB (%25). Detay sütunu ekleme modunda
`md:basis-[70%]`, düzenlemede `md:basis-[50%]` idi.

Ekleme modunda Notlar sütunu (`!yeniMi`) gizleniyor, ama bıraktığı **%25'i kimse
devralmıyordu**: hiçbir sütunda `grow` yoktu. Toplam 70 + 25 = **%95** kalıyor,
sağda %5'lik boş şerit oluşuyordu.

Sabit yüzde aritmetiğini düzeltmek yerine (75 yazsak da gelecekte bir sütun daha
gizlenince aynı hata döner) Detay sütunu **`md:flex-1`** yapıldı: kalan alanın
tamamını emer. Düzenlemede 100−25−25 = %50, eklemede 100−25 = %75 — ikisi de
kendiliğinden doğru.

Ayrıca üç sütuna da `min-w-0` eklendi (flex öğesinin varsayılan `min-width:auto`
değeri uzun içerikte sütunu genişletir — Dosya Yöneticisi'ndeki aynı sınıf hata).

### Kullanıcılar — firma değişikliği sonrası satırın sessizce kaybolması

Şikayet: "firma seçimini değiştiriyorum, Firmalar sütunu güncellenmiyor".

Okumakla yetinmeyip kontrolcü seviyesinde uçtan uca ölçüldü. İki senaryo:

| Senaryo | Kayıt | Liste |
|---|---|---|
| Aktif firma KORUNUP başka firma eklenir | ✓ doğru | ✓ sütun güncelleniyor |
| Aktif firma seçimden ÇIKARILIR | ✓ doğru | ✗ **satır komple düşüyor** |

Yani ne kaydetme ne de sütun bozuktu. `kullanicilariCek()` listeyi **aktif
firmaya** göre süzüyordu ve bu süzgeç BAKAN kişi sistem admin olsa bile
uygulanıyordu. Kullanıcı aktif firmadan çıkarılınca kayıt başarıyla yapılıyor,
"Kaydedildi" mesajı çıkıyor, ama satır listeden yok oluyordu — dışarıdan
"kaydetmedi / sütun güncellenmedi / hiçbir şey çalışmıyor" olarak görünüyor.

Düzeltmeler:

1. **Sistem admin için aktif firma süzgeci kaldırıldı** (`$firmaId > 0 && !$isAdmin`).
   Sistem admin tüm kullanıcıları yönetir; listesi firma seçiminden bağımsız olmalı.
   Test: aktif firma çıkarıldıktan sonra satır artık `ZZ Firma B, ZZ Firma C` ile
   listede görünüyor (önce "BULUNAMADI" idi).
2. **Sistem admin olmayanlar için sessizlik kaldırıldı.** Onlarda süzgeç
   *doğru* davranış (kullanıcı gerçekten başka firmaya taşındı), ama sebep
   söylenmiyordu. Kaydetme sonrası, düzenlenen kişi aktif firmada değilse
   bilgilendirme mesajı çıkıyor: hangi firmadan çıkarıldığı ve neden listede
   görünmediği yazıyor.

**Bilgilendirme modalı güncellendi** (`modulBilgi.ts` → `kullanici`): yeni
"Listede kimler görünür" bölümü (admin → tüm kullanıcılar, diğerleri → seçili
firma), Firmalar sütununun kaydedince güncellendiği, ve uyarılara aktif firmadan
çıkarma durumu eklendi.

## 2026-08-18 — Muhasebe + Stok kaldırıldı, Müşteri/Cari kurgusu başladı

### 1) yonet.php sadeleştirildi
3289 → 212 satır. Tek grup, tek buton: **Tam Deploy**. Ulaşılamaz kalan ~2500
satırlık `case` bloğu ve test-verisi planı da silindi (silinen modüllerin
modellerine referans veriyorlardı). Sayfa render testiyle doğrulandı: 1 grup, 1 buton.

### 2) Muhasebe ve Stok modülleri tamamen kaldırıldı
**Veritabanı** (`2026_08_18_000001_muhasebe_stok_modullerini_kaldir`):
13 tablo (cariler, kasalar, hareketler, urunler, stok_hareketleri, yorum ve
tanım tabloları) düşürüldü; `personel_odemeleri.kasa_id/hareket_id` ve
`musteriler.cari_id` kolonları kaldırıldı; belge/yetki/log/özel alan satırları
temizlendi. `down()` bilinçli olarak boş — geri alma eski modülü diriltirdi.

**Kod**: 5 controller, 13 model, 5 sayfa klasörü, 7 rapor ekranı, 7 migration,
3 seeder ve 2 konsol komutu silindi. Referans temizliği: Moduller, Permissions,
routes, AuditObserver, AlanEtiketleri, AktiviteLog, Ara, Belge, Dashboard, Gorev,
Kategori, Musteri, PersonelOdeme, Rapor, DosyaYonetici + frontend
(ModulModalProvider, AsyncSelect, GenelArama, GorevFormModal, Dashboard.tsx,
modulBilgi.ts).

**Davranış değişikliği:** personel ödemesi artık kasa/muhasebe hareketi üretmiyor.

**Yol boyunca çıkan latent hata:** `roller_kaldir` migration'ı SQLite'ta
`users.rol_id` FK'sını sarkık bırakıyordu ("dropColumn tabloyu yeniden kurar"
varsayımı güncel SQLite'ta geçersiz; Laravel yerel `DROP COLUMN` kullanıyor).
Bu yüzden SONRAKİ HER kolon düşürme şema doğrulamasında patlıyordu. FK ayrıca
düşürülecek şekilde düzeltildi.

**Doğrulama:** sıfır veritabanında tüm migration seti baştan sona çalıştı; 13
tablonun tamamı yok, 3 bağımlı kolon yok. `route:list` 415 → 336. tsc + build temiz.

### 3) Müşteri/Cari kurgusu — ilk adım
* Modül grubu **"Müşteri/Cari Yönetimi"** oldu; yetki grubu "Müşteri/Cari".
* `2026_08_18_000002_cariye_ekle_isaretleri`: teklifler / yenileme_kayitlari /
  projeler / servisler tablolarına `muhasebelestir` + `muhasebelestirme_tarihi`
  ve firma+bayrak indeksi.
* **`CariKaynaklari`** (tek tanım): hangi parasal modülün tutarı nereden okunur.
  Yeni parasal modül eklenince sadece buraya satır eklenir.
* **`MusteriCariController`**: müşteri bazında alacak özeti + müşteri detayı +
  ortak `muhasebelestir` ucu. **Ayrı cari tablosu YOK** — tutar kaydın kendisinden
  okunur, kopyalanmaz; kayıt güncellenince cari kendiliğinden doğru kalır.
* Ekranlar: `MusteriCari/Index` (özet + genel toplam), `MusteriCari/Detay` (döküm).
* **`CariyeEkleButonu`** ortak bileşeni Teklif/Proje/Yenileme toplu işlem
  çubuğuna eklendi (Teknik Servis'in tablo ekranında BulkActionBar yok — sırada).
* Yetkiler: `musteri_cari.goruntule`, `musteri_cari.muhasebelestir`.
* **Bilgilendirme modalı** `musteri_cari` girdisiyle eklendi.

### Düzeltme — sunucuda migrate hatası (MySQL 1091) + kapsam tamamlandı

**Hata:** `Can't DROP 'personel_odemeleri_kasa_id_foreign'; check that column/key exists`

**Sebep (benim kusurum):** `kolonDusur()` içinde try/catch, `Schema::table()`
kapanışının İÇİNDEYDİ. Blueprint komutları kuyruğa alınır ve kapanış bittikten
SONRA çalıştırılır; dolayısıyla catch yalnızca "kuyruğa ekleme"yi sarmalıyor,
gerçek `ALTER TABLE`'ı değil. FK yoksa MySQL 1091 veriyor ve istisna dışarı
kaçıyordu. SQLite'ta hata üretmediği için lokalde görünmemişti.
→ try/catch `Schema::table()` çağrısının DIŞINA alındı.

Migration ilk adımda patladığı için sunucuda hiçbir şey uygulanmadı ve satır
`migrations` tablosuna yazılmadı; Tam Deploy tekrar çalıştırıldığında baştan
geçer.

**Kapsam denetimi (kullanıcı sorusu üzerine):** `musteri_id` taşıyan 9 tablo
tarandı. Parasal alanı VEYA fiyatlı satır tablosu olanlar tam olarak dört:
teklifler, servisler, projeler, yenileme_kayitlari — dördünde de bayrak var.
Diğer beşi (randevular, musteri_sozlesmeleri, musteri_yetkilileri,
musteri_yorumlari, sozlesme_imza_davetleri) ne parasal alan ne ürün satırı
taşıyor; kapsam dışı olmaları doğru.

**Teknik Servis arayüzü tamamlandı:** Tablo ekranında toplu seçim altyapısı
(başlık + satır checkbox'ları, IndeterminateCheckbox, BulkActionBar) yoktu,
kuruldu ve "Cariye Ekle / Muhasebeleştir" butonu eklendi. Artık dört modülün
dördünde de hem bayrak hem buton var.

### Düzeltme 2 — MySQL 1828: FK adı VARSAYILAMAZ

**Hata:** `Cannot drop column 'kasa_id': needed in a foreign key constraint
'personel_odemeleri_ibfk_2'`

Önceki düzeltme (try/catch'i dışarı almak) doğruydu ama **asıl sorunu maskeledi**.
Gerçek sebep: `dropForeign(['kasa_id'])` kısıt adını KURALA GÖRE üretir
(`personel_odemeleri_kasa_id_foreign`). Sunucudaki kısıtın adı ise
`personel_odemeleri_ibfk_2` — yani MySQL'in kendi verdiği ad. Bu, kısıtın ham SQL
ile ya da bir yedekten geri yükleme sırasında oluştuğunu gösterir. Kurala göre
üretilen ad bulunamayınca kısıt yerinde kalıyor, ardından kolon 1828 ile düşmüyordu.

**Çözüm:** MySQL/MariaDB'de kısıt adı artık VARSAYILMIYOR, `information_schema`'dan
okunuyor: kolona bağlı tüm FK'lar gerçek adlarıyla `DROP FOREIGN KEY` ediliyor,
ardından kalan indeksler temizleniyor, sonra kolon düşürülüyor. MySQL dışı
sürücülerde (SQLite) eski kurala-göre yol korunuyor.

`roller_kaldir` migration'ı zaten bu deseni kullanıyordu ve sunucuda sorunsuz
çalışmıştı; aynı desen buraya taşındı.

**Not:** `Schema::disableForeignKeyConstraints()` (FOREIGN_KEY_CHECKS=0) TABLO
düşürmeye izin verir ama FK'da kullanılan bir KOLONU düşürmeye izin vermez —
bu yüzden açık FK temizliği zorunlu.

Sıfır veritabanında (SQLite yolu) doğrulandı. MySQL dalı lokalde çalıştırılamıyor;
yerel sürücü sqlite.

### Cari: ayrı bölüm kaldırıldı, müşteri satırından erişim

İstek: menüde "Cari Listesi" diye ayrı bir bölüm olmasın; Müşteriler listesinde
sözleşme ikonunun yanında o müşterinin carisine geçen bir ikon olsun.

* **Sidebar girdisi** ve **toplu cari listesi ekranı** kaldırıldı
  (`MusteriCari/Index.tsx` silindi, `index()` + `musteriOzetleri()` metotları
  kontrolcüden çıkarıldı, `musteri-cari.index` rotası kaldırıldı).
* **Müşteriler listesine cüzdan ikonu** eklendi — sözleşme ikonunun hemen yanında,
  `musteri_cari.goruntule` yetkisine bağlı, `/musteri-cari/{id}/` adresine gider.
* Cari **detay** ekranı kaldı; geri bağlantısı artık `/musteriler/`, ayrıca
  bilgilendirme butonu eklendi.
* **Sondaki "/" denetimi:** ilgili tüm adresler kanonik —
  `/musteri-cari/{id}/`, `/musteri-cari/muhasebelestir/`, `/musteriler/`,
  kayıt bağlantıları `${yol}/`. Rota eşleşmesi ikisi için de fiilen test edildi
  (POST ve GET, sondaki slash ile doğru rotaya düşüyor).
* **Bilgilendirme modalı** güncellendi: bu ekrana Müşteriler listesindeki cüzdan
  ikonuyla girildiği ve tüm müşterileri toplayan ayrı bir ekran olmadığı yazıldı.

### Kalıntı taraması — `Firma::cariler()` ve diğerleri

Bildirilen hata: `Call to undefined method App\Models\Firma::cariler()`.

**Neden ilk temizlikte kaçtı:** Eloquent ilişkileri yalnızca `->cariler()` diye
çağrılmaz; `withCount(['cariler'])`, `with('kasa')`, `whereHas('hareketler')`
gibi **dize** olarak da anılır. Bunlar metot-çağrısı desenine takılmaz ve ancak
çalışma anında patlar. `FirmaController::firmalariCek()` tam olarak bunu
yapıyordu.

**Tahmin yerine tarayıcı:** dize-tabanlı ilişki referansları, silinen sınıflara
atıf, controller'larda tanımsız `$this->metot()` ve tanımsız rota adlarını
denetleyen bir tarayıcı yazıldı (yansıma + regex).

Bulunan ve düzeltilenler:
* `FirmaController` — `withCount(['cariler','kasalar',...])` ve
  `cariler_count/kasalar_count` sıralama kolonları.
* `Firma/Index.tsx` — cari/kasa sütunları, `muhasebeAcik` bayrağı, colSpan payı.
* `DashboardController`, `RaporController` — ölü `use` satırları.
* `Personel/OdemeYonet.tsx` — kasa seçici (`/api/ara/kasalar`), "kasaya borç
  hareketi işlenir" bandı, tablo Kasa sütunu, `kasa_id` state'i. (Backend'den
  kasa entegrasyonu kaldırılmıştı, ekran sarkıyordu.)

**Duman testi:** 73 GET sayfası gerçek HTTP çekirdeğinden geçirildi.
* 3 sayfa hata verdi (`/raporlar/randevu` + pdf/excel) — sebep `CONCAT`
  fonksiyonunun SQLite'ta olmaması. **Yerel sürücü artefaktı**, sunucu MySQL.
* `bildirimler` ve `gorevler` tabloları depodaki hiçbir migration tarafından
  oluşturulmuyor (önceden var olan eksik; silinen 7 dosyanın hiçbirinde yoktu).
  Bu yüzden yerelde bu tablolara dokunan sayfalar test edilemiyor.
* Kalan sayfaların tamamı açıldı.

**Cari mantığı fiilen doğrulandı:** işaretli proje (tutar kolonu 1500) + işaretli
teklif (satırlardan 2×250 + 1×100 = 600) → döküm 2 satır, toplam 2.100,00.
İşaretsiz teklif (9×9999) dökümde ÇIKMADI. Beklenen = gerçekleşen.

### Raporlar tek çatı altında toplandı

Önceden her modülün yan menüsünde ayrı bir "Raporlar" bağlantısı vardı (5 ayrı
giriş). Artık tek modül, tek bağlantı, tek sayfa.

* **`App\Support\Raporlar`** — tüm raporların TEK tanımı. Her rapor `modul` +
  `yetki` taşır; bu ikisi rapor rotalarındaki `modul:` + `yetki:` middleware
  çiftiyle BİREBİR aynıdır.
* **`RaporController::index()`** listeyi SUNUCUDA süzer
  (`Raporlar::gorunur($user, $aktifModuller)`). Kullanıcının açamayacağı rapor
  ekrana hiç düşmez; düşse bile adres elle yazıldığında middleware engeller.
  Yetki kuralı tek yerde tutuluyor — istemcide ikinci bir kontrol yok, iki yerde
  kural tutmak ayrışmaya davetiye olurdu.
* **Yan menü:** 5 modüldeki "Raporlar" girdisi kaldırıldı; yeni `raporlar`
  modülünde tek girdi. Görünürlük ÇOKLU yetki ifadesine bağlı
  (`musteri.rapor|teklif.rapor|...`) — rapor yetkilerinden en az biri yoksa
  menüde hiç çıkmaz.
* **`User::yetkiKontrol()` çoklu yetki desteği kazandı** (`a|b` → herhangi biri).
  `yetki:` route middleware'i bu yazımı zaten destekliyordu; aynı sözleşme
  modele taşındı ki yan menü ve ekranlarda da kullanılabilsin.
* Hub ekranı (`Rapor/Index.tsx`) sunucudan gelen listeyi çizer; hiç rapor yoksa
  sebebini açıklayan bilgilendirici kutu gösterir.

**Yetki süzmesi fiilen test edildi** (3 senaryo, beklenen = gerçekleşen):

| Kullanıcı | Menü | Görünen rapor |
|---|---|---|
| Admin | görünür | 6/6 |
| Sadece `teklif.rapor` | görünür | 1/6 (yalnızca teklif) |
| Hiç rapor yetkisi yok | **gizli** | 0/6 |

**Bilgilendirme modalı** `raporlar` girdisiyle eklendi; hangi raporun neden
göründüğü/görünmediği açıkça yazıldı.

### Raporlara ortak derinlik katmanı

İstek: raporlar mümkün olduğunca ayrıntılı olsun.

Altı rapora ayrı ayrı elle metrik eklemek yerine **tek bir analiz katmanı**
yazıldı; birinde düzeltilip diğerinde unutulan hesap olmasın.

**`App\Support\RaporAnaliz`** — bir rapordan yalnızca "taze sorgu döndüren
kapanış" + tarih kolonu (+ varsa tutar kolonu) alır, şunları üretir:

* **Önceki dönemle karşılaştırma** — seçili aralıkla AYNI uzunlukta, hemen
  öncesindeki dönem otomatik hesaplanır. Adet ve tutar için fark + yüzde + yön.
  Önceki dönem 0 ise yüzde **null** döner ("%∞ artış" yanıltıcı olurdu); arayüz
  "yeni" yazar.
* **Günlük seri** — kayıt olmayan günler 0 ile doldurulur, boşluklar görünür kalır.
* **Kümülatif seri** — dönem içi birikim.
* **Hafta günü dağılımı** — Pazartesi→Pazar.
* **Yoğunluk** — günlük ortalama, dolu/boş gün sayısı, en yoğun gün.
* **Tutar istatistikleri** — toplam, ortalama, **medyan**, min, max.

Sürücü farkı tek yerde: tarih ifadeleri SQLite (`strftime`) ve MySQL
(`DATE_FORMAT`/`DAYOFWEEK`) için ayrı üretilir; hafta günü iki sürücüde de
0=Pazar tabanına getirilir.

**Bağlandığı raporlar:** Müşteri, Teklif, Proje (tutar analizi açık — `projeler.tutar`),
Görev, Randevu. Frontend'de ortak **`RaporAnalizPaneli`** bileşeni çizer.

**Doğrulama (bilinen veriyle, 12 kontrol):** bu dönem 3 proje (100/300/500),
önceki dönem 1 proje (200) →
adet 3 vs 1 (+2, %200), tutar 900 vs 200 (+700, %350), medyan 300, ortalama 300,
min 100, max 500, dolu gün 2, seri 31 gün, kümülatif son 900,
hafta günü Pazartesi=2 (3 Ağustos gerçekten Pazartesi). **12/12 doğru.**

Duman testi: yeni hata yok; kalan 3 hata önceden bilinen SQLite `CONCAT`
eksikliği (sunucu MySQL).

Bilgilendirme modalı (`raporlar`) yeni "Her raporda bulunan detay blokları"
bölümüyle genişletildi; medyanın neden ortalamadan farklı okunduğu da yazıldı.

### Düzeltme — "0 / 31.999999999988425" ve blok boşlukları

**1) Kesirli gün sayısı (benim hatam).**
`RaporAnaliz::paket()` içinde `$basC->diffInDays($bitC) + 1` kullanılıyordu.
Carbon 3'te `diffInDays()` **float** döner; `$basC` startOfDay, `$bitC` endOfDay
olduğu için fark 30,99999… çıkıyor, +1 ile **31,999999999988425** oluyordu.
Ekranda "Kayıt olan gün: 0 / 31.999999999988425" olarak görülüyordu.

İki tarafı da gün başına sabitleyip `(int)` çevirdikten sonra +1 yapılıyor.
Yan etki olarak **önceki dönem sınırı da düzeldi**: 31 günlük Ağustos için önceki
dönem 30 Haziran–31 Temmuz yerine artık doğru şekilde 1–31 Temmuz.

Farklı uzunluklarda doğrulandı — hepsinde tam sayı:
`Ağustos=31`, `tüm yıl=365`, `tek gün=1`, `Şubat=28`.
Analiz testinin 12 kontrolü de geçmeye devam ediyor.

**2) Blok boşlukları.**
Panel, `mb-4` kullanan kardeş blokların arasına **marjsız** giriyordu; bir sonraki
bloğa yapışıyordu. Ayrıca alt bloklar (karşılaştırma / yoğunluk / istatistik /
grafikler) sayfaya dağılmış gibi duruyordu.
→ Panel tek bir **"Dönem Analizi"** bölümüne alındı (çerçeve + başlık + dönem
etiketi), köke `mb-6` verildi (kardeşler `mb-4`; panel daha yoğun olduğu için bir
kademe fazlası), iç ızgara boşlukları kardeşlerle aynı ölçüye (`gap-3`) getirildi
ve bölümler arası `space-y-5` yapıldı. `yazdirma-butun` sınıfıyla yazdırmada da
bölünmüyor.

### Personel izinleri (zaten mevcut) + bilgilendirme modalından yetki listesi kaldırıldı

**1) Personel bazlı izin ekranı ZATEN VAR — yeniden yazılmadı.**
Mesai ikonuyla birebir aynı desen kurulu durumda:
* Yetki: `personel.izin` (Permissions'ta tanımlı)
* İkon: Personel listesinde `CalendarOff`, mesai/ödeme ikonlarının yanında
* Rota: `GET personeller/{personel}/izin` → `personel.izin.karti`
  (+ `izin/ozet`, `izin/hak` uçları)
* Ekran: `PersonelIzin/PersonelKarti.tsx`

Rota eşleşmesi fiilen test edildi: `/personeller/5/izin/` → `personel.izin.karti`,
`/personeller/5/mesai/` → `personel.mesai.index`. Sondaki "/" ile ikisi de doğru
rotaya düşüyor. Yeni kod yazmak yerine mevcut yapı doğrulandı.

**2) Bilgilendirme modalından "Gereken Yetkiler" bölümü kaldırıldı.**
Modal son kullanıcıya modülün ne işe yaradığını anlatıyor; `musteri.rapor` gibi
yetki kodları teknik ayrıntı ve orada gürültü yapıyordu.
Veri tarafındaki `yetkiler` alanı **korundu** (silinmedi, tipte duruyor) —
yalnızca ekranda çizilmiyor. Kullanılmayan `ShieldCheck` import'u da temizlendi.

**Bilgilendirme kapsamı (denetlendi):** 28 konu tanımlı, 30 ekran ModulBilgi
kullanıyor. Bilgilendirmesi OLMAYAN ekranlar: **Kategori/Index** ve
**Ayarlar/Index** (Proje/Index yalnızca yönlendirme yapan bir ara ekran,
TeknikServis/Index boş dosya — ikisi de kapsam dışı). Bu iki ekran açık eksik
olarak not edildi.

### Bilgilendirme kapsamı tamamlandı

Önceki turda açık eksik olarak not ettiğim iki ekrana bilgilendirme modalı eklendi:

* **Tanımlamalar** (`Kategori/Index`) — konu `tanimlamalar`. İçerik: tanımların
  firmaya özel olduğu, sıra ve renk alanlarının nereyi etkilediği, pasife almanın
  eski kayıtları bozmadığı, silmeden önce bağlı kayıt sayımının gösterildiği ve
  bağlı kayıt varsa silmenin engellendiği.
* **Sistem Ayarları** (`Ayarlar/Index`) — konu `ayarlar`. İçerik: bölümlerin ne
  yaptığı, sekme değiştirmenin kaydetmediği, e-posta/SMS ayarı sonrası test
  gönderiminin neden şart olduğu (hatalı ayar sessizce başarısız olur), görsel
  yüklemede önbellek yenileme, ayarların firma bazlı DEĞİL sistem geneli olduğu.

Bu iki ekranda `actions` prop'u hiç yoktu; bilgilendirme butonu için eklendi.

**Kapsam denetimi:** 32 ekran ModulBilgi kullanıyor. Bilgilendirmesi olmayan
yalnızca iki dosya kaldı ve ikisi de gerçek ekran değil:
`Proje/Index` (yalnızca `/projeler/board/` adresine yönlendiren ara ekran) ve
`TeknikServis/Index` (boş dosya).

**Tarama sonucu:** frontend kalıntı tarayıcısı **0 sorun** — ölü modül adresi yok,
`modulBilgi.ts`'te olmayan konu anahtarı yok, tanımsız modal kodu yok.
Duman testi: yeni hata yok (kalan 3 hata bilinen SQLite `CONCAT` eksikliği).

### İzinler yan menüden kaldırıldı + "modal sayfaya gitmez" kuralı

**1) Personel > İzinler yan menü girdisi kaldırıldı.**
İzin artık mesai/ödeme ile aynı desende: personel satırındaki ikondan
(`/personeller/{id}/izin/`) açılıyor. Mesai ve ödemenin yan menüde ayrı girdisi
yokken izinde olması tutarsızdı. Personel modülü menüsü artık yalnızca
"Personeller".

**2) YENİ KESİN KURAL — modal, kendi sayfasına giderek açılmaz.**
Bir modülün ekle/düzenle modalı kullanıcı HANGİ SAYFADAYSA orada açılmalı.
Modal'ı açmak için yönlendirme yapmak yasak: kullanıcı o an bir formu
dolduruyorsa sayfa değişince girdiği veri kaybolur.

**Bu kuralı ben ihlal etmiştim ve düzeltildi:** `ParaBirimiFormModal`, sağlayıcıdan
açıldığında `/para-birimleri/?yeni=1` adresine yönlendiriyordu (iki ayrı form
tutmamak için seçilmiş bir kısayoldu, yanlıştı).

Doğru çözüm uygulandı: `AyarModal` + `TcmbCanliListe` ayrı dosyaya çıkarıldı
(`ParaBirimi/_AyarModal.tsx`, 610 satır). Artık hem liste sayfası hem sağlayıcı
AYNI ortak formu render ediyor; modal her sayfada yerinde açılıyor.
`Index.tsx` 930 → 368 satıra indi.

**Denetim:** `router.visit(...?yeni=1)` deseni kod tabanında **kalmadı**.
tsc + build temiz, frontend kalıntı taraması 0 sorun, duman testinde yeni hata yok.

Kural kalıcı hafızaya yazıldı (`feedback_modal_sayfaya_gitmez`): yeni modül
eklerken modal sağlayıcıya mount edilir, ekle/düzenle formu TEK ortak bileşen
olur, yönlendirme ile idare edilmez.

### Derin test verisi üretici

İstek: Tanımlamalar ve Sistem Yönetimi grupları HARİÇ tüm sistemlere derin test
verisi yükleyen bir buton.

**`DerinTestVeriSeeder`** — kapsam: müşteri (+yetkili +iletişim +yorum), müşteri
sözleşmesi, personel (+mesai +ödeme +izin), proje (+yorum), randevu,
teklif (+ürün kataloğu +satır +yorum), servis (+yedek parça +satır +yorum),
yenileme (+ürün +satır +yorum).
**Kapsam dışı (bilinçli):** Kullanıcılar, Tanımlamalar, Para Birimleri, Firmalar,
Sistem Ayarları, Dosya Yöneticisi, Günlük Kayıtları.

Tasarım kararları:
* Her kayıt **`TESTVERI`** etiketi taşır; temizlik bu etikete bakar, gerçek veriye
  asla dokunulmaz.
* Kayıtlar **son 12 aya yayılır** — raporlardaki dönem karşılaştırması, günlük seri
  ve hafta günü dağılımı ancak böyle anlamlı çıkar.
* Alt kayıtlar ana kayıtla birlikte üretilir; "boş kabuk" kayıt bırakılmaz.
* Bir kısmı **`muhasebelestir`** işaretli üretilir ki müşteri cari listesi dolu gelsin.
* Eksik tanım tabloları (durum/statü/tip) gerekiyorsa üretilir (bazıları NOT NULL).
* Tablo/kolon yoksa o bölüm sessizce atlanır — şema farkı seeder'ı patlatmaz.

Şemaya uyum sırasında düzeltilenler (gerçek kolon adları okunarak):
`musteri_yetkilileri` ad+soyad (ad_soyad yok), `musteri_yetkili_iletisim.tip`
(tip_kod değil), `mesai_kayitlari` normal_saat+fazla_mesai (calisilan_saat yok),
yorum tablolarında metin kolonu `yorum`/`mesaj` değişkenliği, `yenileme_yorumlari.kayit_id`,
tanım tablolarında NOT NULL `kod`.

**Doğrulama:** tek firmada çalıştırıldı → **1.525 ilişkili kayıt**
(müşteri 40, teklif 40 + 134 satır, servis 40 + 113 satır, yenileme 40 + 98 satır,
proje 40, randevu 40, personel 13 + 238 mesai + 61 ödeme + 32 izin, sözleşme 20,
yorumlar 321, katalog 17). İşlem rollback ile geri alındı.

**`yonet.php`:** "🧪 Test Verisi" grubu eklendi — *Yükle*, *Silinecekleri Listele*
(kuru çalıştırma) ve *Sil* (onay kutusu şartlı). Sayfa render testiyle doğrulandı:
2 grup, 4 buton.

## 2026-08-19 — Kasa/Banka + Cari Hareket altyapısı (FAZ 1: veri katmanı)

İstek: carilere gelen ödeme / giden ödeme / tahsilat girilebilsin; kasa ve banka
sistemi olsun. Fatura sonraki aşamada.

### Araştırma sonucu tasarım kararları

**1) Kasa ve Banka TEK tabloda (`odeme_araclari`, `tip` alanıyla).**
İkisi de "para kabı"; bakiye hesabı, hareket kaydı ve raporlama mantıkları
birebir aynı. Ayrı tablo yapmak aynı kodu iki kez yazmak ve zamanla ayrıştırmak
olurdu. Bankaya özgü alanlar (banka_adi, sube, hesap_no, iban) nullable olarak
aynı satırda; kasada boş kalır. `acilis_bakiye` = sisteme geçmeden önceki devir.

**2) `cari_hareketler` — ödeme/tahsilat tarafı.**
`yon` muhasebenin temel ayrımı: `alacak` (cari borçlandı) / `borc` (borcu azaldı).
`tip` iş dilindeki karşılık: tahsilat, gelen_odeme, giden_odeme.
**Yön elle yazılmaz, tipten TÜRETİLİR** (model `saving` kancası) — yanlış yön
girilip bakiyenin ters dönmesi mümkün değil.

**3) ALACAK TARAFI BU TABLOYA YAZILMAZ.**
Teklif/proje/servis/yenileme kayıtları "Cariye Ekle" ile işaretlendiğinde alacak
olarak zaten okunuyor (`CariKaynaklari`). Tutarın kopyası burada da dursaydı,
kayıt güncellenince iki değer ayrışırdı. Bu tablo yalnızca para hareketidir.

**4) Kasa bakiyesi ile cari bakiye AYNI kayıttan türetilir** (`odeme_araci_id`),
böylece mutabakat yapısal olarak bozulamaz. Dikkat edilen nokta: cari yönü ile
kasa yönü TERSTİR — tahsilat carinin borcunu azaltır ama kasayı ARTIRIR.

### Yapılanlar
* `2026_08_19_000001_kasa_banka_cari_hareket` migration (2 tablo + indeksler)
* `App\Models\OdemeAraci` — `bakiye` accessor (açılış + giren − çıkan)
* `App\Models\CariHareket` — yön türetme + `tutar_tl` otomatik hesabı

### Doğrulama (bilinen veriyle)
Kasa açılış 1000, tahsilat +2500, giden ödeme −400 → **3100** ✓
Banka açılış 5000, gelen ödeme +1500 → **6500** ✓
Yönler: tahsilat/gelen_odeme = `borc`, giden_odeme = `alacak` ✓
Cariye ödenen net: 2500 + 1500 − 400 = **3600** ✓

### SIRADA (bu turda YAPILMADI)
Controller + rotalar + yetkiler, Kasalar/Bankalar liste ekranı, cari detayında
hareket girişi modalı (kural gereği sayfada açılacak), cari bakiyenin
"alacak (işaretli kayıtlar) − ödemeler" olarak birleştirilmesi, modül kaydı ve
bilgilendirme modalı.
