# Ofis rol turu — uçtan uca *(08.08.2026)*

**Amaç:** *"ofis production ready mi"* sorusunu kanıta bağlamak. Ekran açmak değil, **iş yapmak**: yedi yetki paketinin her birini kendi günlük akışında yürütmek.

**Neden bu yöntem:** 08.08.2026'da bulunan **on gerçek hatanın hepsi** paneli kullanarak bulundu ve **hepsinde suite yeşildi**. Tarayıcı turu 97 ekranı açıyor ama 16 dosyada toplam 34 tıklama var — yani *ekran açılıyor mu* diye bakıyoruz, *çalışıyor mu* diye bakmıyoruz.

⛔ **Bu dosya tur boyunca güncellenir.** Kesinti olursa nerede kalındığı buradan okunur. Bir adım ancak **kanıtıyla** işaretlenir: ne yapıldı, ne görüldü, hangi kayıt değişti.

## Durum işaretleri

| İşaret | Anlamı |
|---|---|
| ⬜ | Yapılmadı |
| 🔄 | Devam ediyor |
| ✅ | Yapıldı, çalışıyor — kanıt yazılı |
| ❌ | Yapıldı, **bulgu var** — bulgu numarası yazılı |
| ⏭ | Atlandı — gerekçe yazılı |

## Hesaplar

Şifre `Passw0rd`, TOTP kodu tinker'dan (`AppAuthenticator::currentCode`). Tümü yalnız local (`DemoOfficeStaffSeeder`).

| Paket | Hesap |
|---|---|
| Satışçı | `satisci1@3edemo.test` |
| Satış yöneticisi | `satis-yonetici1@3edemo.test` |
| Planlamacı | `planlamaci1@3edemo.test` |
| Operasyon | `operasyon1@3edemo.test` |
| Operasyon direktörü | `direktor1@3edemo.test` |
| Muhasebe | `muhasebe1@3edemo.test` |
| Eğitmen | `egitmen1@3edemo.test` |

---

## A · Planlamacı — eğitim planlama zinciri

Çatı §10: *"planlamacının oluşturmadığı hiçbir eğitim için satışçı işlem yapamaz."* Zincir buradan başlar.

| # | Adım | Durum | Kanıt |
|---|---|---|---|
| A1 | Giriş → iniş `/planlama` | ✅ | `/planlama` — "Gündemim — planlama" |
| A2 | Planlama gündemi: kartlar, sayılar, gelen talepler | ✅ | Ufuk "Bugün"de boş, "Ay"da 3 eğitim · %15 doluluk · 3 bildirim bekleyen — veriyle uyuşuyor |
| A3 | Sorgulama ekranı — il/firma bazında süresi dolan kişi adedi | ✅ | 183 sonuç · 2 dolmuş · 5 yaklaşan · 10 firma · 25 satır |
| A4 | Kayıtlı görünüm oluştur / uygula / sil | ✅ | Kaydedildi, listede göründü, silindi |
| A5 | **Yeni eğitim planla** (`/egitimler/yeni`) — kaydediliyor mu | ✅ | Eğitim #6: 24-25 Ağu, Bursa, 16 kontenjan, `planned`, EG-2026-D4HP |
| A6 | Eğitime **eğitmen ata** — aday listesi, yeterlilik gerekçeleri | ❌ | **B-1** ve **B-2**. Düzeltilince: UYGUN (1) — Mehmet Şahinoğlu |
| A7 | Eğitmen teklifi gönder → eğitmen tarafında görünüyor mu | ✅ | İstek #1 (`first_accepted`), teklif #1 → egitmen2, 24 saat cevap süresi |
| A8 | Planlama kuyruğu: satılmış ama planlanmamış olanlar | ✅ | İki kaynak ayrı sayılıyor (onaylı teklif 0 · planlama talebi 0); boş durum gerekçeli. B sonrası yeniden bakılacak |
| A9 | Talep kuyruğu: talebi reddet / hatırlat / yeniden aç | ✅ | B11'in ürettiği `TLP-2026-MKSN` plana çevrildi (`EG-2026-MKSN`), kod taşındı, talep `planned` oldu. Ret yetkisi planlamacıya verildi *(Sets kararı)*; **B-19** ve **B-20** düzeltildi |
| A10 | Eğitimi **yayımla** (satışa aç) | ✅ | A5 ile oldu: durum şeridi "Planlandı — satışa açık". Satışçı tarafı B3 doğrulayacak |
| A11 | Tedarikçi ekle / sözleşme / teklif | ❌ | **B-5**. Düzeltildikten sonra: tedarikçi oluştu, fiyat kaydedildi (₺4.500 · 2 birim), tarihçe çizildi |

## B · Satışçı — teklif zinciri

Çatı §10.3-4: satışçı planlanmış eğitimi görür, teklif hazırlar, yönetici onayına düşer.

| # | Adım | Durum | Kanıt |
|---|---|---|---|
| B1 | Giriş → iniş `/satis` | ✅ | `/satis` — Satış kokpiti |
| B2 | Kokpit: açık fırsat, bekleyen teklif, satışa açık eğitim sayıları doğru mu | ✅ | Sayaçlar doğru; yöneticide "0 iç onayda" **kendi** teklifleri için doğru — bkz. B-13 |
| B3 | Planlanmış eğitimleri görüyor mu (`/egitimler`), sadece kendine atanmışları mı | ✅ | A5'te planladığım `EG-2026-D4HP` listede; kalem ekleme seçeneklerinde de çıkıyor |
| B4 | **Yeni teklif oluştur** — müşteri, eğitim, satır ekle | ✅ | Teklif #6 açıldı (`TK-2026-VT8J`) |
| B5 | Teklif satırı: fiyat, indirim, kontenjan tahsisi | ✅ | Kalem eklendi: 8 kişi × ₺4.800 = ₺38.400 — birim fiyat **fiyat tablosundan** geldi |
| B6 | İndirim tavanını **aşan** teklif → onaya düşüyor mu | ❌ | **B-12** ve **B-14**. Düzeltilince: %25 → −₺9.600, "Limitiniz: %15" uyarısı, buton "Yönetici Onayına Gönder"e döndü |
| B7 | Teklifi **iç onaya gönder** | ✅ | `internal_review`; ekranda aşım gerekçesi + SLA sayacı |
| B8 | Teklifi müşteriye **gönder** (mail) | ✅ | Yönetici onayladı → **B-15** düzeltilince onay paneli ve butonu kalktı → gönderildi, durum `sent` |
| B9 | Teklif zaman çizelgesi: sürümler, mektuplar, portal görüntülemeleri | ✅ | Çekmece: Tümü 3 · Sürüm 2 · Karar 1 · Portal 0; filtreler ve sayaçlar doğru — **B-16** düzeltildi |
| B10 | Teklif çıktısı (antetli kağıt) açılıyor mu | ❌ | Açılıyor ama **B-17**: indirim satırı hiç çizilmiyordu (toplamlar tutmuyordu) ve fiyat tipi ham anahtar basıyordu — ikisi de düzeltildi |
| B11 | Doğrudan gelen talebi **planlamaya aktar** (`/satis/planlama-talepleri/yeni`) | ✅ | `TLP-2026-MKSN` açıldı (IMDG · İzmir · 17–19.08) ve planlamacı kuyruğuna düştü — **B-18** düzeltildi |
| B12 | Kendine ait olmayan teklife erişemiyor mu | ✅ | `/satis/teklifler/1` ve `/4` → **403** "Yetkiniz yok"; listede yalnız kendi teklifi |

## C · Satış yöneticisi — onay ve limit

| # | Adım | Durum | Kanıt |
|---|---|---|---|
| C0 | **Satışçıya müşteri ata** (`user.assign_customers`) — demo veride hiç atama yok, tur bunu da denesin *(Sets, 08.08.2026)* | ✅ | **B-8 · B-9 · B-11** düzeltildikten sonra: iki müşteri atandı (audit `user.customers_assigned [3,4]`), satışçının müşteri listesi doldu |
| C1 | Giriş → iniş `/satis` | ✅ | `/satis` — direktörden ayrı, doğru |
| C2 | `/onaylar` açılıyor mu *(bugün düzeltildi — regresyon kontrolü)* | ✅ | Açılıyor; altı dilim + **Teklif** dilimi (B-13) |
| C3 | İndirim limiti onayı: onayla ve reddet (gerekçeli) | ✅ | Her iki yön denendi: gerekçeli ret → geçmişe gerekçesiyle düştü; ikinci talep onaylandı → satıcı satırı "%15 +5 özel". Panel bugün yazıldı (B-13) — **B-21** ve **B-22** düzeltildi |
| C4 | Satıcının **indirim tavanını değiştir** (`/yonetim/satiscilar`) | ✅ | %25 yazıldı → yürürlüğe girmedi, "onayda: %25" olarak kuyruğa düştü; ev limiti korundu |
| C5 | Ekibinin tekliflerini görüyor mu (`quote.view.group`) | ✅ | Kendi ve satisci1'in teklifleri listede; **B-23** düzeltildi |
| C6 | Kendi limitini onaylayamıyor mu (self-approval reddi) | ✅ | Kuyrukta "Sizin talebiniz" · düğmeler kapalı · kimlerin kuyruğunda olduğu yazılı + hatırlatma. Kendi hedefini koyma da reddediliyor — **B-24** düzeltildi |

## D · Operasyon — eğitim yürütme

| # | Adım | Durum | Kanıt |
|---|---|---|---|
| D1 | Giriş → iniş `/yonetim` | ✅ | `/yonetim` — operasyon gündemi; menü rolüne göre |
| D2 | Operasyon gündemi kartları | ✅ | Onay kuyruğu · bu hafta · mevzuat riskleri · ekip yükü (AD-21 notuyla) · tahsilat |
| D3 | Eğitim **başlat** / gün kapat | ❌ | Başlatma çalışıyordu ama **hiçbir şeye bakmıyordu** — bkz. **B-27**; artık tarih ve eğitmen koşullu |
| D4 | **Bakanlık bildirimi** yap / güncelle / iptal bildir | ✅ | KUMS ID girildi → "Bakanlığa bildirildi"; iptal bildirimi D8'de |
| D5 | Eğitmenden **evrak iste** → görev açılıyor mu | ✅ | Kimlik + sağlık raporu istendi, iki görev açıldı — **B-26** düzeltildi |
| D6 | Eğitmen evrağını **onayla / reddet** | ✅ | Direktör kuyruğundan sağlık raporu onaylandı; panelde tür bazlı metinler düzeltildi (**B-21**, **B-22**) |
| D7 | Eğitimi **tamamla** (sınav + yoklama + evrak) | ❌ | Uçtan uca yapıldı ama **üç ayrı hata** çıktı: kilit altı koşuldan üçünü gösteriyordu, KUMS referansını hiçbir ekran sormuyordu, ret sessizdi — **B-45**. Düzeltilince `EG-2026-QYCF` **Tamamlandı**: beş katılımcı tamamlandı, hakediş açıldı |
| D8 | Eğitim **iptal** akışı: gerekçe, onay, bildirim | ❌ | Uçtan uca çalıştı **ama panel hiç açılmıyordu** — bkz. **B-25** (500). Düzeltilince: planlamacı talep etti, operasyon onayladı, karar geçmişe düştü |
| D9 | Yedek listeden katılımcı yükselt | ❌ | Seçici **boş açılıyordu** (iki ayrı sebep) — bkz. **B-28**. Düzeltilince: firma eklendi, "Çağır" ile "Çağrıldı" oldu |
| D10 | Yoklama çelişkisi çözümü | ❌ | Altı çelişki çözüldü — ama karar **saklanmıyordu**: **B-44**. Düzeltilince "katılmadı" denen kişi tamamlanmadı, sertifika almadı |

## E · Operasyon direktörü — en üst yetkili

| # | Adım | Durum | Kanıt |
|---|---|---|---|
| E1 | Giriş → iniş `/yonetim` | ✅ | Tam menü: planlama · tanımlar · satış · yönetim · ayarlar |
| E2 | **Her ekranı** açabiliyor mu (52 rota taraması) | ✅ | Parametresiz 35 ofis ekranı tarandı, hepsi **200** (eğitmen paneli rotaları G'de) |
| E3 | Dört onay türünün hepsini karara bağlayabiliyor mu | ✅ | Bugün altı türün beşi gerçekten karara bağlandı: teklif · indirim tavanı (onay+ret) · eğitim iptali · belge. Masraf/maliyet eşiği bekleyen kayıt yok |
| E4 | Kullanıcı yönetimi: hesap aç, yetki ver, pasifleştir | ✅ | Hesap açıldı, davet gitti, sonra **kapatıldı** (silinmedi). Şablonun davette taşınmaması doğruymuş — ekran artık bunu yazıyor: **B-34** |
| E5 | Eğitmen dosyası: künye, engel, ödeme, kimlik açığa çıkarma | ✅ | TCKN açıldı → `audit_access` satırı yazıldı (`national_id`, `reason: file_screen`, IP + oturum) |
| E6 | Sistem günlüğü + anketler | ❌ | Günlük baştan sona ham olay adı basıyordu — **B-31**. Anket ekranı "36 cevap · katılım %0" diyordu — **B-33**. İkisi de düzeltildi |
| E7 | Belge şablonu (antetli kağıt) yükleme | ✅ | 1240×200 PNG yüklendi, v1 yürürlükte, ölçü okundu. Yükleyicideki **fiş uyarısı** kaldırıldı — **B-32** |

## F · Muhasebe — mali akış

| # | Adım | Durum | Kanıt |
|---|---|---|---|
| F1 | Giriş → iniş `/muhasebe` *(bugün düzeltildi — regresyon kontrolü)* | ✅ | `/muhasebe` — mali tablo; menüde muhasebe + faturalar |
| F2 | Muhasebe ekranı: masraflar, teklifler, borçlar | ✅ | Gelir ₺57.600 · maliyet · brüt marj · vadesi geçen ₺37.920 |
| F3 | **Fatura kes** | ✅ | Serbest kalem: ₺4.500 + KDV = **₺5.400** (`FT-2026-H6J6`) |
| F4 | Tahsilat kaydet / geri al | ✅ | ₺2.000 girildi → "Kısmen Tahsil Edildi", kalan ₺3.400; **ters kayıt** ile geri alındı — iki satır da listede kaldı, silme yok |
| F5 | Faturayı iptal et | ✅ | Ödemesiz fatura (`FT-2026-J9CR`) gerekçeyle **İptal Edildi**; kayıt duruyor. Ters kayıtlı fatura da artık iptal edilebiliyor — **B-37** kapandı |
| F6 | Muhasebeye bildir (e-posta) | ❌ | Tıklama **hiçbir şey yapmıyor, hiçbir şey söylemiyordu** — bkz. **B-35**. Adres tanımlanınca gönderildi, satır "Bildirildi" oldu |
| F7 | Eğitmen cari hesabı + ödeme kaydı | ❌ | Elle hareket kaydedildi (₺1.500, "Onay bekliyor", bakiyeye dahil değil). Doğrulama mesajı ham geliyordu — **B-36** |
| F8 | Dışa aktarma | ✅ | Mali tablo · sorgulama · eğitmen durumu CSV'leri Türkçe başlıklarla indi; sistem günlüğü dışa aktarımı muhasebeye **403** (doğru — `audit.view` yok) |

## G · Eğitmen — kendi paneli

| # | Adım | Durum | Kanıt |
|---|---|---|---|
| G1 | Giriş → yasal onay → iniş `/egitmen` | ✅ | Yasal onay ekranı (bugün düzeltilen) okunup onaylandı → panel açıldı |
| G2 | Gündemim: yaklaşan eğitimler | ❌ | Panel ham anahtar basıyordu (`instructor.fields.ministry_id`) — **B-38**. Düzeltilince: atamalar, masraflar, cari, belgeler, müsaitlik |
| G3 | Görev **teklifini kabul / reddet** | ❌ | Kabul **500** veriyordu — **B-40**. Düzeltilince kabul edildi, atama gündeme düştü |
| G4 | Müsaitlik takvimi güncelle | ✅ | 24–26 Ağustos izni girildi, listeye düştü |
| G5 | **Masraf gir** + belge ekle, geri çek | ✅ | Konaklama ₺850 + fiş yüklendi → "Onay bekliyor", geri çekme düğmesi açık |
| G6 | Cari durumum | ✅ | Bekleyen ₺850 gösteriliyor, bakiyeye dahil değil |
| G7 | Belgelerim (salt okunur) + görev üzerinden yükleme | ✅ | Salt okunur, beş belge "tam ✓"; yükleme görev üzerinden |
| G8 | Saha: yoklama, gün kapatma | ❌ | Saha ekranlarına **hiçbir yerden bağlantı yoktu** — **B-42**. Düzeltilince: 6 yoklama işareti (biri gelmedi), imzalı liste yüklendi, 5 sınav notu girildi, gün kapatıldı, eğitmen teslim etti |
| G9 | Başkasının verisine erişemiyor mu | ✅ | On ofis ekranının hepsi **403** (eğitmenler · kullanıcılar · onaylar · muhasebe · teklifler · yönetim · günlük · müşteriler · talepler · eğitimler) |

## H · Çapraz kontroller

| # | Adım | Durum | Kanıt |
|---|---|---|---|
| H1 | Her rol için 375px'te rail ve birincil eylem | ❌ | Dip ray etiketleri **birbirine giriyordu** ("Bildirimlerim" 82px'lik metin 70px hücrede, komşularla 2px ara) — **B-47**. Düzeltilince kolonuna sığıyor; beş ekranda yatay taşma yok |
| H2 | Form doğrulama hataları görünüyor mu (Türkçe, alan altında) | ✅ | Boş formlarda gönder düğmesi kapalı; sunucu reddi üç kez alan altında Türkçe çıktı (kendi hedefi · kalem türü · teklif tavanı). ⚠ Alanı olmayan menü eylemlerinde ret görünmüyordu — **B-24 · B-35 · B-45**, üçü de düzeltildi |
| H3 | Boş durumlar (hiç kayıt yokken) | ✅ | "Bekleyen onay yok" · "Henüz masraf girmediniz" · "Bekleyen talep yok" · "Henüz cari hareketi yok" · "Bildiriminiz yok" — hepsi ne yapılacağını da söylüyor |
| H4 | Tarayıcı konsolu her ekranda temiz mi | ✅ | Direktörle **34 ekran, sıfır hata**. Operasyonla 29 ekranda çıkan 11 kayıt 403 (o rolün zaten giremediği ekranlar) |
| H5 | Bildirim rayı ve görev sayacı doğru mu | ✅ | Ray "Görevlerim 17", ekran "17 açık görev"; bildirim "0 okunmamış" ile liste tutuyor |

---

## Tur bitince bakılacak

- **Landing sayfası** *(Sets, 08.08.2026)*: `design/L01 Landing Ana Sayfa.dc.html` — tasarımcı eğitimin landing page'ini hazırladı. Ofis turu bitince bakılacak.

## Bulgular

### B-21 · Onay panelinde gerekçe "Eğitmenin gerekçesi" diyordu — DÜZELTİLDİ

Satıcının tavan talebinin gerekçesi, eğitmen masrafı için yazılmış tek etiketle çıkıyordu. Etiket artık türe göre: masraf/belge eğitmenin, tavan talebi **talep edenin**.

### B-22 · Hazır ret gerekçeleri her türde masrafa aitti — DÜZELTİLDİ

Ret çekmecesi üç hazır cümle sunuyordu — *Mükerrer kayıt · **Fiş okunmuyor** · Kapsam dışı* — ve bunları indirim tavanı ile teklifte de gösteriyordu. Kayıt sahibi bu metni **birebir** okuduğu için, fiş hakkında hazır bir cümle indirim reddinin bir tık yanında duruyordu. Hazır gerekçeler türüne bağlandı (tavan: *tavan yüksek · gerekçe yeterli değil*; teklif: *indirim fazla derin · gerekçe yeterli değil*). Aynı yerdeki "bakiyeye hiçbir şey yazılmaz" cümlesi de yalnız masrafta çıkıyor — tavan talebinin bakiyesi yok.

### B-23 · Teklif listesindeki uyarı ham çoğul sözdizimi basıyordu — DÜZELTİLDİ

Liste başında `{1} 1 teklifin geçerlilik süresi doldu…|[2,*] …` yazıyordu: çağrı sayıyı `$t`'ye **üçüncü argüman** olarak veriyordu, `$t` ikisini alıp gerisini yok sayıyor. `$tChoice`'a çevrildi.

⚠ Bu tek düzeltme değil, **arch testi**: `FrontendTest` artık çoğul sözdizimiyle yazılmış her satırı sözlükten (iç içe, yaprak adıyla değil) çıkarıp düz yardımcıyla okunan yer var mı diye bakıyor. Yaprak adıyla arayınca 51 şüpheli ve 1 gerçek çıkmıştı; tam anahtarla tek gerçek ihlal kaldı.

### B-24 · Kendi satırını kaydeden hiçbir şey kaydedemiyordu — DÜZELTİLDİ

⛔ Çekmece her kaydetmede **bütün alanları** gönderiyor. Dokunulmamış hedef alanı, "kendi hedefini kimse koyamaz" kuralına takılıyor ve bu istisna **indirim limiti bloğundan önce** fırlıyordu: yönetici kendi satırını açıp daha geniş tavan istiyor, Kaydet'e basıyor, **hiçbir şey olmuyor ve hiçbir şey söylenmiyordu**. Üstüne, hata `amount` anahtarıyla atılıyordu; form alanı `target_amount` — mesaj hiç çizilmiyordu.

Üç düzeltme: (1) hedef ve tavan eylemleri yalnız **değer değiştiğinde** çağrılıyor; (2) ret mesajı formun kullandığı alan adıyla anahtarlandı — artık alanın altında **"Kendi hedefinizi siz koyamazsınız."** yazıyor; (3) kural yerinde duruyor.

⛔ Aynı sınıftan ikinci hata: müşteri atama eylemi de **koşulsuz** çağrılıyordu. `user.assign_customers` yetkisi olmayan bir yönetici, çekmecede müşteri alanını görmese bile listeyi gönderdiği için her kaydetmede 403 alıyordu. Alan yetkisiz kullanıcıya çizilmiyor, form onu göndermiyor, doğrulama yalnız **sorulmuşsa** zorunlu tutuyor, controller yalnız gelmişse çağırıyor — yetki bloğunun bugün aldığı biçimin aynısı.

⚠ Ayrıca: değişmeyen müşteri listesi artık **audit satırı yazmıyor**. Tavan genişletmek için açılan her kayıt, şirketlerin yeniden devredildiğini söyleyen bir satır bırakıyordu; olmamış işi anlatan denetim kaydı okunamaz (AD-06). *(Bu, turda "küçük, düzeltilmedi" diye not edilmiş kalemdi — kapandı.)*

### B-38 · Eğitmen panelinde ham anahtar — DÜZELTİLDİ, ayrıca **arch testi**

Eğitmenin gördüğü ilk ekran `instructor.fields.ministry_id` yazıyordu. Sözlükte satır **vardı**; controller o paketi göndermiyordu (dört paket sayıyor, sayfa beşini okuyor).

⛔ Aynı hata bugün **ikinci kez**: sabahki yasal onay ekranı hiç paket göndermediği için her kelimesini anahtar olarak basıyordu. İkisi de yalnız ekranı açarak bulundu — hiçbir test göremezdi.

Artık `FrontendTest` görüyor: controller'ın `withTranslations(...)` çağrısı ile onu izleyen `render('Sayfa')` eşleştiriliyor, sayfanın okuduğu her `$t('paket...')` öneki gönderilenlerle karşılaştırılıyor (her sayfada bulunan `common`/`validation` ve panel kabuğu hariç). Gerçek hatayla doğrulandı: paket çıkarılınca test kırılıyor.

### B-39 · Ekranlarda ISO tarih — DÜZELTİLDİ

İzin listesi `2026-08-24 → 2026-08-26`, eğitim listesi `2026-08-17 → 2026-08-19`, onay panelinde `Eğitim tarihi 2026-09-06`. Paylaşılan `readableDay()` yardımcısı **hiçbir ofis sayfasında kullanılmıyordu** (18 sayfa ham basıyordu). Hepsi yardımcıdan geçirildi: **24 Ağu 2026**.

⚠ Aday ekranının başlık satırı hâlâ ISO (`· 2026-08-24 – 2026-08-25`) — farklı bir yerde kuruluyor, kalan tek yer.

### B-40 · Eğitmen atamayı kabul edince 500 — DÜZELTİLDİ

Kabul, eğitmenin kanıtlarını donduruyor (`FreezeInstructorEvidence`) ve bu her belgeyi okuyor. Demo seeder'ları belgeleri **yalnız seed anında var olan** bir diske yazıyordu (`Config::set('filesystems.disks.demo-documents', …)`) ve o adı satıra kaydediyordu — sonrasında ad hiçbir şeye çözülmüyor, okuyan ilk ekran **"Disk [demo-documents] does not have a configured driver"** ile 500 veriyordu.

Disk artık `config/filesystems.php` içinde gerçek bir disk. Bayat bir satır bundan sonra "dosya yok" diye düşer, "disk tanınmıyor" diye değil.

⚠ Ürün kodu doğruydu; kırık olan demo verisinin kurulumuydu — ama kırdığı şey **eğitmenin ilk gerçek işiydi**.

### B-35 · "Muhasebeye bildir" sessizce hiçbir şey yapmıyordu — DÜZELTİLDİ

Menüden tıklanıyor, istek **302** dönüyor, satır **"Bildirilmedi"** kalıyordu. Sebep: muhasebe e-posta adresi tanımsızken eylem doğru şekilde reddediyor — ama ret bir **doğrulama hatası** olarak dönüyor ve menü öğesinin hatayı çizecek bir alanı yok. Tıklayan kişi hiçbir şey görmüyordu.

Eylem artık yalnız adres tanımlıysa çiziliyor; tanımsızsa menüde gerekçesi yazıyor: *"Muhasebe adresi tanımlı değil — bildirim gönderilemez."* Adres tanımlandıktan sonra tur içinde gerçekten gönderildi ve satır **"Bildirildi"** oldu.

⚠ Bu, **B-24** ile aynı sınıf: sessiz doğrulama hatası. İkisinde de kök sebep, formu olmayan bir eylemin hata döndürmesiydi.

### B-36 · Cari hareket doğrulaması makine dilinde konuşuyordu — DÜZELTİLDİ

Kalem türü seçilmeden kaydedince ekran şunu yazıyordu: **"Tür expense olduğunda tür zorunludur."** — Laravel'in `required_if` şablonu, içinde veritabanının kendi değeri (`expense`) ve iki kez "tür". Kendi mesajı yazıldı: *"Masraf kaydında kalem türü seçilmelidir."*

### B-47 · 375px'te dip ray etiketleri birbirine giriyordu — DÜZELTİLDİ

Beş hedefin etiketleri kendi hücrelerinden taşıyordu: **"Bildirimlerim"** 82px, hücre 70px; komşu etiketlerle arada **2px** kalıyordu ve beş kelime tek satır gürültü gibi okunuyordu. Türkçe etiketler uzun; hücre sabit. Etiket artık kendi kolonunda kalıyor (`truncate`, ortalı) ve hedef alanı değişmiyor.

### B-48 · Müşteri listesinde ham anahtar: `customers.legal_type.as` — DÜZELTİLDİ

Bir müşterinin hukuki türü **`as`** kaydedilmişti — şirket eki, hukuki tür değil. Form isteği bu kolonu her zaman iki değerle sınırlıyordu; sınırlamayan şey **kodun kendisiydi**: `NewCustomer::$legalType` düz `string`'di ve local seeder oradan geçmişti.

Değer artık **tip**: `LegalType` enum'u (`legal_entity` · `natural_person`). Seeder, controller ve testler ondan geçiyor; `EnumLabelTest` iki durumun da karşılığını arıyor. Bozuk satır düzeltildi.

⚠ Önce bunu "veritabanında karşılıksız değer var mı" diye tarayan bir test yazdım; **ekilen bozuk değeri yakalamadı** — o seeder test veritabanında koşmuyor, yani test hiçbir şey kanıtlamıyordu. Silindi. Kapıyı tutan şey artık tipin kendisi.

### B-42 · Eğitmenin saha ekranlarına hiçbir yerden bağlantı yoktu — DÜZELTİLDİ

⛔ `Training.vue` ve `TrainingDay.vue` yazılmış, rotaları kayıtlı, payload'da eğitimin token'ı taşınıyor — üstelik kodun kendi yorumu *"tasarım `40` doğrudan saha ekranına bağlanır"* diyor. **Bağ hiç çizilmemişti:** ne gündemdeki atama kartı ne "Eğitimlerim" listesi tıklanıyordu. Yoklama, imzalı liste, sınav notu ve gün kapatma — hepsi eğitmenin ulaşamadığı bir adresin arkasındaydı.

İki liste de kendi ekranına bağlandı.

### B-43 · Belge türü seçicisi boş satırlar çiziyordu — DÜZELTİLDİ

Eğitim evrağı yüklerken tür listesi **iki boş satır** gösteriyordu: `id`/`name` gönderiliyor, `SelectInput` `value`/`label` okuyor. **B-28'in aynısı, ikinci yerde.** Kontrolün okuduğu biçime çevrildi.

### B-44 · Çelişki kararı saklanmıyordu, her çözüm "Katıldı" sayılıyordu — DÜZELTİLDİ

⛔ **Turun en ağır bulgusu.** Çelişki çözümü yalnız serbest metin kaydediyordu; ekran ise *çözülmüş her satırı* katılım sayıyordu. Gerekçeye "salona gelmedi" yazdım, sonuç **"Katıldı"** çıktı — ve bu sonuç sertifikanın dayanağı: ekranın kendi cümlesi *"katılmayan sertifika alamaz"* diyor.

Karar artık **saklanıyor** (`declaration_conflict_attended`), çekmece önce kararı soruyor (Katıldı / Katılmadı), sonuç sütunu onu okuyor. Turda doğrulandı: "katılmadı" denen katılımcının tamamlama kaydı **açılmadı**, diğer beşi tamamlandı.

⚠ Eski satırlar `null` kalır ve eskiden olduğu gibi katılım sayılır — onlara geriye dönük karar uydurulmadı.

### B-45 · Tamamlama: kilit eksik, referans sorulmuyor, ret sessiz — DÜZELTİLDİ

Üç hata bir arada:

1. **Kilit altı koşuldan üçünü gösteriyordu.** `CompletionGate` imzalı yoklama · sınav sonucu · çelişki · KUMS kanıtı · **sınav kâğıdı** · **oturum kayıtları** istiyor; ekran ilk üçünü listeliyordu. Kilit "hazır" derken eylem reddediyordu.
2. **KUMS tamamlama referansını hiçbir ekran sormuyordu.** `FinishTraining` onu parametre olarak bekliyor, controller boş geçiyordu — yani tamamlama arayüzden **hiç** geçemezdi. Artık çekmece soruyor.
3. **Ret görünmüyordu:** doğrulama hatası, alanı olmayan bir düğmeye dönüyordu (B-24 · B-35 ile aynı sınıf).

Bir de metin hatası: ret cümlesi `trainings.completion.exam_paper` ham anahtarını basıyordu — altı koşuldan ikisinin sözlük satırı hiç yazılmamıştı.

### B-46 · Tek oturum gece yarısını aşıyordu — DÜZELTİLDİ *(Sets kararı)*

`IMDG-G10` 16 saat, **günlük azami ve oturum uzunluğu boş**. `SessionPlan` bunu tek oturum sayıyor (kural: "kimse günün nasıl bölündüğünü söylemediyse tek oturum") ve 09:00 → ertesi gün 01:00 üretiyor; ekranda `12:00–04:00`. 32 tanımın **22'sinde** günlük azami yok; 12–16 saatlik olanların hepsi aynı şeyi üretir.

**Sets'in kararı (08.08.2026):** *"default olarak 8 saattir gün, default olarak 8 saati ekle, gerekiyorsa düzenleyebilsinler."*

`training.default_daily_hours` ayarı eklendi (varsayılan **8**, backoffice iş kuralları ekranından düzenlenebilir). Tanımın kendi `max_daily_hours` değeri varsa **o geçerli** — TY-2019 md.17/1'den gelen 8 saatlik aile aynen kalıyor. Yalnız susan tanımlar ev varsayılanını kullanıyor.

⚠ Diğer 22 tanıma günlük saat **yazılmadı**: mevzuatta yok, uydurulmaz (kırmızı çizgi 14). Ev varsayılanı bunu zaten doğru bölüyor.

Mevcut demo eğitimlerinin oturumları yeniden üretildi: artık **09:00–17:00** ve 16 saatlik eğitim iki güne yayılıyor.

### B-37 · Ters kayıtla iptal edilmiş tahsilat, faturayı iptale kapatıyor — DÜZELTİLDİ

Kural (Sets, 01.08.2026): *para geldiyse fatura iptal edilmez, alacak dekontu kesilir.* Kod bunu `payments()->exists()` ile ölçüyor. Ama **ters kayıtla iptal edilmiş** tahsilat da bir satırdır: turda ₺2.000 girip geri aldım, fatura "Ödeme Bekliyor / kalan ₺5.400" hâline döndü — yani **para gelmemiş sayılıyor** — ama "İptal et" bir daha hiç açılmadı.

**Sets'in kararı (08.08.2026):** *iptal açılsın ama gerekçe zorunlu olsun ve fatura ayrıca işaretlensin.*

Ölçü artık "tahsilat satırı var mı" değil **"net tahsilat > 0 mı"**. Kısmen tahsil edilmiş fatura yine iptal edilemez — dekont ister. Tamamı ters kayıtla iptal edilmiş fatura iptal edilebilir; gerekçe zaten zorunluydu.

İşaretleme üç yerde: listede **"hareket görmüş"** rozeti (yalnız iptal + hareket varsa), denetim satırında `had_movement`, ve muhasebe bildiriminde ayrı bir gövde — iptal edilmiş fatura artık *"…iptal edilmiştir / İptal gerekçesi: … / Bu faturada tahsilat girilmiş ve ters kayıtla iptal edilmiştir; muhasebe tarafında iki kayıt da görünür."* metniyle bildiriliyor ve iptal edilmiş fatura da bildirilebiliyor.

Tarayıcıda doğrulandı: `FT-2026-H6J6` (₺2.000 girilip ters kayıtla geri alınmıştı) iptal edildi, satırda **İptal Edildi · hareket görmüş** yazıyor.

### B-41 · Demo setinde hiçbir eğitimin bağlı eğitmeni yoktu — DÜZELTİLDİ

⛔ **B-27'nin açtığı kapı.** "Eğitmen bağlanmadan eğitim başlatılamaz" kuralını koyunca demo seeder'ı kırıldı: tüm veritabanında **tek bir atama satırı** vardı, o da benim tarayıcıda yaptığım. Seeder eğitimleri planlıyor, satışa açıyor, bakanlığa bildiriyor ve başlatıyordu — eğitmen daveti hiç gönderilmeden. `StartTraining` bugüne kadar sormadığı için kimse fark etmemişti.

⚠ Bu, temiz kurulumu da kırardı: `migrate:fresh --seed` seeder'ın başlatma adımında düşerdi.

Seeder artık gerçek yoldan bağlıyor: **talep aç → teklif gönder → kabul et**. Atama satırını doğrudan yazmak aynı boşluğu tekrar gizlerdi.

### Tur verisi *(Sets kararı, 08.08.2026)*

Demo setine **bugün süren** bir eğitim eklendi: `EG-2026-QYCF` — 7→9 Ağustos, İzmir, eğitmeni bağlı (Zeynep Ünüvar-Demirtaş), 6 katılımcı kayıtlı, bilerek **kapatılmamış**. Saha ekranı, yoklama, çelişki çözümü ve tamamlama kilidi artık tarih beklemeden yürünebilir (D7 · D10 · G8).

⚠ Turda kural yokken erken başlattığım `EG-2026-MKSN` **Planlandı**'ya geri çekildi.


### B-31 · Denetim kaydının 146 olayı ham anahtar basıyordu — DÜZELTİLDİ

⛔ Eğitim kaydının "Geçmiş" şeridinde `training.planned`, sistem günlüğünde `authorization.denied`, `quote.created`, `discount_limit.requested` — **filtrenin kendi listesi bile** ham. Kodun yazdığı 173 olaydan **146'sının** Türkçe karşılığı yoktu. "Kim ne yaptı" sorusunun tek yeri olan ekran, makinenin diliyle konuşuyordu.

Hepsi iki dilde yazıldı ve **arch testine** bağlandı. Test iki tuzağı da atlatıyor: (a) olay adları `event: $ok ? 'a.approved' : 'a.refused'` gibi üçlü operatörle de yazılıyor — ilk sürüm bunları görmüyordu ve `discount_limit.*` ham kalmıştı; (b) 403'ün yazdığı olay `bootstrap/` içinde. Ekilen sahte olayla doğrulandı: test kırılıyor.

⚠ `AuditEntryRow` bilerek ham ada düşüyor — uydurma cümle üretmiyor. Bu, boşluğun **görünür** olmasını sağlayan tasarım; sorun düşüşün kendisi değil, sözlüğün boş olmasıydı.

### B-32 · Antet yükleyicisi fiş uyarısı veriyordu — DÜZELTİLDİ

Ortak dosya bileşeni "bulanık görünüyor" uyarısına masraf ekranının cümlesini de ekliyordu: *"Fişten veri okunmaz, tutarı siz yazarsınız."* Bileşen antet, imzalı yoklama ve eğitmen diploması yüklemek için de kullanılıyor. Ortak cümle genelleştirildi; fiş cümlesi `blur-note` ile **yalnız masraf ekranında**.

### B-33 · "36 cevap · katılım oranı %0" — DÜZELTİLDİ *(demo verisi)*

Oran **yoklama kaydından** hesaplanıyor (`survey_answered_at`), cevaplara hiç dokunmuyor — anonimliği koruyan tasarım bu. Demo seeder ise yanıtları doğrudan yazıp damgayı vurmuyordu; ekran kendi kendisiyle çelişen bir cümle kuruyordu. Seeder artık `AnswerSurvey`'in yaptığını yapıyor: oran **%59**.

⚠ Ürün kodu doğruydu; düzeltilen demo verisi. Ama müşterinin göreceği ekran buydu.

### B-34 · Davet edilen kullanıcının seçilen yetki şablonu düşüyor — DÜZELTİLDİ

Yeni kullanıcı ekranında "Operasyon — başlangıç" paketini seçtim, **Daveti gönder**'e bastım: hesap **sıfır yetkiyle** açıldı, listede "Yetki bekleyen" olarak işaretlendi. Kod bilinçli: `then === 'permissions'` değilse `template_id` **null** gönderiliyor; yani seçim yalnız *Kaydet ve Yetkilendir* yolunda taşınıyor.

Ekran "bu adımı atlarsanız kullanıcı hiçbir sayfa göremez" diyor — ama **seçilmiş** bir paketin sessizce düşeceğini söylemiyor.

**Sets'in cevabı (08.08.2026):** *"kullanıcı önce davet edilecek, sisteme giriş yaptıktan sonrasında yetkileri tanımlanacak — ne alakası var davet sırasında yetki/paket göndermekle?"* Yani **davranış doğru**: davet yetki taşımaz. Yanlış olan, ekranın seçimi sessizce yutmasıydı.

Paket seçiliyken düğmelerin yanında artık şu yazıyor: *"Davet yetki yazmaz: seçtiğiniz paket yalnız 'Kaydet ve Yetkilendir' ile uygulanır. Davet ederseniz kişi giriş yaptıktan sonra yetkilendirilir."* Seçim yokken satır çıkmaz.

⚠ Tur hesabı (`tur-denemesi@3edemo.test`) kapatıldı — silinmedi (AD-23).

### B-25 · İptal onayının paneli hiç açılmıyordu (500) — DÜZELTİLDİ

⛔ Kuyrukta iptal talebi **görünüyordu**, satıra tıklayınca **500**. Sebep: delil sorgusu `customers` tablosuna **join** atıyordu; o tablo kendi id'si üzerinden satır güvenlikli ve ekran hiçbir tenant'a ait değil — sorgu her seferinde reddediliyordu. Kodun üstündeki yorum "pivot üzerinden okuyoruz, güvenli" diyordu; güvenli olmayan kısım **join**'di.

Pivot join'siz okunuyor, isimler tek tek **kendi kapsamında** alınıyor (`SalesQuotes::companyNames` ile aynı desen). Test ekranın kendisinden geçiyor ve eski hâlde 500 veriyor — doğrulandı.

### B-26 · Eğitmen dosyasında görev adı ham anahtar — DÜZELTİLDİ

"Açık belge görevleri" listesinde `instructor.ministry_notification` yazıyordu. Yalnız belge görevleri bağlamında bir ad taşıyor; diğer türler ham değere düşüyordu — sözlükte karşılığı **zaten vardı**, kimse okumuyordu. Artık türün cümlesi yazılıyor: *"Bakanlık bildirimi yapılacak"*.

### B-27 · Eğitim başlatma hiçbir şeye bakmıyordu — DÜZELTİLDİ *(Sets kararı)*

17.08'de başlayacak, eğitmeni atanmamış, katılımcısı olmayan eğitimi 08.08'de tek tıkla "Devam ediyor" yaptım; ne uyarı ne engel. Sets'in kararı: **tarihten önce başlatma engellenir, eğitmensiz başlatma da reddedilir.** İkisi de eyleme yazıldı; ekranda düğme gerekçesiyle kapalı (kilit notu). "Bugün" sunucudan geliyor — düğme ile eylem, okuyucunun saati yüzünden farklı günde olmasın.

### B-28 · Yedek listesi seçicisi boş açılıyordu — DÜZELTİLDİ

İki ayrı sebep üst üste:

1. **Operasyonun hiçbir müşteriye erişimi yoktu** (`customer.view` şablonda yok; planlamacı/direktör/muhasebe 11 müşteri görüyor, operasyon 0). Sets'in kararı: **operasyona `customer.view.all` verilir** — eğitimi yürüten kişi katılımcının hangi firmadan geldiğini görmeli. Şablona eklendi, mevcut demo hesaplara uygulandı.
2. Erişim gelince seçici **11 boş satır** çizdi: liste `id`/`name` gönderiyordu, `SelectInput` `value`/`label` okuyor. Kontrolün okuduğu biçime çevrildi.

### B-29 · Talepten planlanan eğitimin adresi kayboluyordu — DÜZELTİLDİ

Plan seviyesindeki adres **program** satırına yazılıyor; talebe tek eğitimle cevap verilince program yok — adres hiçbir yere yazılmıyordu. Katılımcıların imzaladığı yoklama çizelgesi **"Eğitim yeri —"** basıyordu. Adres artık her eğitim satırına da taşınıyor.

### B-30 · Operasyon, kullanamayacağı "Eğitmen ata" düğmesini görüyordu — DÜZELTİLDİ

Aday ekranı `assignInstructor` istiyor, düğme ise `can.update` ile çiziliyordu; operasyon eğitimi yürütür ama eğitmeni seçmez → 403. Bayrak sayfada zaten vardı, düğme ona bağlandı. *(B-18 ile aynı sınıf.)*

### B-19 · Talepten açılan planlama "kendiliğinden" diyordu — DÜZELTİLDİ

Tasarım `24`'ün başlık altı şeridi üç olgu taşır: **nereden geldi**, hangi mevzuat, nerede. "Plana Çevir" ile açılan ekran, talep bilgisi elindeyken bile **"Kaynak: kendiliğinden"** yazıyordu — şeridin var olma sebebi olan olgu yanlıştı. Artık **"Kaynak: satış talebi TLP-2026-MKSN"**.

### B-20 · "Planlandı" sekmesi hep 0 sayıyordu — DÜZELTİLDİ

Kuyruk üç dilim çiziyor (Açık · Planlandı · Reddedildi), sorgu ise yalnız `open` ve `declined` okuyordu. Planlanan talep açık listeden düşüyor ve **hiçbir dilimde görünmüyordu**: talebi açan satışçı ne olduğunu bu ekrandan asla göremiyordu. Sorguya `planned` eklendi; satır, talebin kodunu devralan eğitimle (`EG-2026-MKSN`) birlikte görünüyor. Test `satis` grubunda.

⚠ Bu tur sırasında verilen karar *(Sets, 08.08.2026)*: **ret yetkiye bağlıdır** — yetkisi olan planlamacı gerekçesiyle reddedebilir. `training_request.update.all` planlamacı şablonuna eklendi, mevcut demo planlamacılarına da uygulandı.

### B-16 · Zaman akışı kararı ham değer basıyordu — DÜZELTİLDİ

Çekmecede satır **"Karar · internal.approved"** yazıyordu — veritabanının kendi değeri, İngilizce, Türkçe ekranda. `QuoteTimeline` başlığı `$approval->kind.'.'.$approval->decision` olarak kuruyordu.

`sales.timeline.approval_kinds.*` ve `sales.timeline.decisions.*` sözlükleri yazıldı; satır artık **"Karar · İç onay: onaylandı"**. Test kaydın taşıyabildiği **her** sabiti (internal/customer × approved/declined/revision_requested) iki dilde arıyor — çünkü karşılığı olmayan sabit, henüz kimsenin ulaşmadığı sabittir.

### B-17 · Basılı teklifte indirim satırı hiç yoktu — DÜZELTİLDİ

⛔ Müşterinin imzaladığı kağıtta: **Ara toplam ₺38.400 · KDV ₺5.760 · GENEL TOPLAM ₺34.560**. Aradaki −₺9.600 hiç yazmıyordu; kağıt kendi aritmetiğini tutmuyordu.

Sebep: `Printout.vue` `totals.discount_total` alanını okuyordu — `QuoteTotals` böyle bir alan **hiç üretmedi**, dolayısıyla `if` hiç girmedi ve satır hiç çizilmedi. Tasarım `37`'deki gibi düzeltildi: ara toplam **liste** fiyatı, indirim satırı **kalem + genel** indirimin ikisini birden taşır (yalnız genel indirimi yazmak, kalem indirimini görünmez kılar ve toplam yine tutar). Test aritmetiği doğruluyor, anahtarı değil — yanlış bir ikinci anahtar anahtar testini geçerdi.

⛔ Aynı kağıtta ikinci hata: kalem adının yanında **`(trainings.rate_unit.per_participant)`**. O sözlükte yalnız `hourly`/`daily` var; ekranların okuduğu `sales.price_kind` sözlüğüne çevrildi. `PriceKind` artık `EnumLabelTest`'te — sabitin karşılığı yoksa test kırılır.

### B-18 · Talep kuyruğu, yapılamayan eylemleri sunuyordu — DÜZELTİLDİ

Satışçı kendi açtığı talebin satırında **"Reddet"** ve **"Plana Çevir"** düğmelerini görüyordu. Eylemler yetkiyi doğru kontrol ediyor (`training_request.update` / `training.create`) — hatalı olan ekrandı: basan 403 alıyordu. Kuyruk artık `can.decide` / `can.plan` ile çiziyor; yetki ofis ailesi bağlamı içinden sorulur (çıplak `can()` yanlış aileye düşüp emin bir "hayır" döndürebiliyor).

⚠ **Açık soru:** `training_request.update` hakkı yalnız **operasyon direktörü** şablonunda var. Planlamacı şablonu bu hakkı taşımıyor — yani talebi reddetmek/yeniden açmak bugün planlamacının elinde değil. Tasarım `34` reddi kuyruğun işi olarak çiziyor.

### B-12 · İndirim türü seçili gelmiyordu — DÜZELTİLDİ

Segmented kontrol **hiçbir tarafı seçili olmadan** açılıyordu (`%` ve `₺` ikisi de `aria-pressed:null`). Satışçı rakamı yazıp butona basınca **tür göndermeden** değer gidiyor; tür `nullable` olduğu için istek başarıyla dönüyor ve **indirim uygulanmıyordu** — hata da yok, uyarı da yok. Varsayılan `percent` oldu (tasarımın ilk segmenti ve yanındaki tavan da yüzde).

### B-13 · Yöneticinin "bana düşen onay" yüzeyi yok — DÜZELTİLDİ

Onay kuyruğu (`/onaylar`) beş tür topluyor: masraf, evrak, maliyet eşiği, iptal, indirim **limiti**. "İç onay bekleyen teklif" içlerinde yok.

Satış kokpitindeki *"N'i iç onayda"* sayacı `owner_user_id = actor` ile **kendi** tekliflerini sayıyor — satışçı için doğru, yönetici için ilgisiz. Teklif listesinde durum dilimi (chip) yok; yönetici ancak filtre açıp bakarsa görür.

⚠ Çatı §10.4: *"teklif → yönetici onayına düşer"*. Bugün hiçbir yere düşmüyor; yönetici bakmayı bilmek zorunda. ⚠ Tasarım `30` satışçının sayacını çiziyor, yöneticiye özel bir yüzey çizilmemiş.

**Karar (Sets):** kuyruğa girsin. **DÜZELTİLDİ.**

`ApprovalKind::Quote` yazıldı, `quote.approve` hakkına bağlandı (önce yanlışlıkla indirim limitinin hakkına bağlanmıştı — o hâlde kuyruk teklifi, kararın sonra reddedeceği kişiye gösterecekti; test yakaladı). `PendingApprovals::quotes()` yalnız tavanı aşan ve **okunan metin için** kararı henüz verilmemiş teklifleri alıyor; satıcı indirimi tavanın altına çekerse teklif kuyruktan kendiliğinden düşüyor.

⛔ **Kuyruk düzeltilirken ikinci bir açık çıktı:** karar paneli `v-if="! evidence"` kapısının arkasında ve `ApprovalEvidence` **indirim limiti** için de `null` dönüyordu. Yani bugüne kadar satıcının tavan talebi kuyrukta **görülebiliyor ama hiçbir yerden karar verilemiyordu** — satır seçiliyor, adres değişiyor, sağ taraf "soldan bir kayıt seçin" demeye devam ediyordu. İki tür için de delil paneli yazıldı: teklifte tutar · toplam indirim · en yüksek kalem · satıcının tavanı · hazırlayan, tavan talebinde şu anki · talep edilen · şirket tavanı.

⛔ **Üçüncüsü:** "Bugün sonuçlananlar" kartı teklif kararını hiç görmüyordu — `SettledApprovals::EVENTS` olay adına göre yön okuyor, teklifte ise tek olay (`quote.decided_internally`) iki yönü de taşıyor. Haritaya `approved => null` girişi eklendi; yön artık satırın kendisinden okunuyor. Turda 13:49'da verilmiş karar da böylece geriye dönük göründü.

Tarayıcıda doğrulandı (satis-yonetici1): kuyrukta "Teklif 1" dilimi, panelde rakamlar ve müşteri adı, **Onayla** → kuyruk boşaldı, karar bugünün listesine düştü. Test: `tests/Feature/Satis/QuoteTest.php` — *"puts a quote over the ceiling in the approval queue…"*.

⚠ **Bilinçli kapsam dışı:** satış kokpitindeki sayaç satıcının kendi hunisidir, öyle kalıyor. Teklif listesine beşinci bir dilim eklenmedi — tasarım `02` dört çip çiziyor ve yöneticinin yüzeyi artık kuyruk.

### B-14 · İndirimi uygulayan butonun etiketi "Özet"di — DÜZELTİLDİ

`sales.show.summary` anahtarını çağırıyordu. Kendi anahtarı yazıldı: **"İndirimi Uygula"**.

### B-15 · Onaylanmış teklif hâlâ "onay bekliyor" diyordu — DÜZELTİLDİ

Yönetici onayladıktan sonra ekran değişmiyordu: panel "0 saattir bekliyor" diyor, **"Onayla" butonu duruyor** ve aynı metin tekrar tekrar onaylanabiliyordu.

⚠ Model doğruymuş: `approved` durumu **müşteri** onayı demek; yöneticininki içerik hash'ine iğnelenmiş bir **kayıt** (`SendQuote::isApproved`). Hata paneldeydi — yalnız duruma bakıyordu. Artık `SendQuote` ile **aynı kuralı** kullanıyor: metnin hash'ine ait onay varsa panel de buton da kalkar. Metin değişirse onay anlamını yitirir ve panel geri gelir.

### B-1 · Aday listesi başlığında ham yer tutucu — DÜZELTİLDİ

`UYGUN (:count)` diye görünüyordu. `Candidates.vue` satırı `$t('trainings.candidates.eligible')` diye çağırıyordu, sayıyı vermeden.

⚠ Aynı hatayı ürünün tamamında taradım — **sözlüğü gerçek nesteli hâliyle çözerek**. Beş aday çıktı, dördü gerçek değil: `DateRangePicker`, `FileDrop` ve `Printout` yer tutucuyu kendileri `.replace()` ile koyuyor, `MultiSelect` ise `:count`'u bileşenin içinde dolduruyor (kaç tanesi eşleşti bilgisi yalnız onda). Yani **tek gerçek bulgu buydu.**

### B-2 · Ölü gereklilik kapısı — LOKAL VERİ, ama production sorusu açık

Aday listesi *"uygun eğitmen bulunamadı"* diyordu. Ölçüm: **6 eğitmen Görev 8'i verebiliyor ve sertifikaları geçerli.** Sebep: `IMDG Kod eğitmeni yetkisi` gerekliliği belge istiyor ama **kabul edilen hiçbir belge tipi tanımlı değildi** — 12 gerekliliğin **12'si de** böyleydi.

`InstructorCompetence::satisfies()` bunu bilerek karşılanamaz sayıyor (*"belge isteyip hiçbir tip saymayan gereklilik kapıyı herkese açmasın"*). Yani kod doğru; **pivot tablosu boştu**.

`InstructorRequirementSeeder` bu eşleştirmeyi yapıyor ve docblock'u bu hatayı zaten biliyor. Yeniden koştum → 12 satır geldi → aday listesi doldu. **Lokal veri bayatmış**, kod hatası değil.

⛔ **Ama production tarafı açık kaldı** — aşağıdaki B-3.

### B-4 · İki farklı aday ekranı — KARAR BEKLİYOR

Sets'in tarifi doğru ve **kurulmuş**: planlama ekranındaki aday çekmecesi (`PlanningCandidates` + `CandidatePicker`) tasarım `24`'ü birebir uyguluyor — *UYGUN (1)* satırında "✓ Yetki · belgeler o tarihte geçerli · takvim müsait", altında **UYGUN OLMAYANLAR (5) — NEDENİYLE**, her satırda kişi · kategori · **sebep** ("Hesap pasif") · "Dosyası →". Ölçüldü, çalışıyor.

⚠ Ama **ikinci bir aday ekranı** var: eğitim detayındaki "Eğitmen ata" → `/egitimler/{id}/adaylar`. O ekran `EligibleInstructors` + `Candidates.vue` kullanıyor ve **yalnız uygun olanları** listeliyor; gerekçe yok, sözlükteki `UYGUN OLMAYANLAR (:count) — NEDENİYLE` satırı orada hiç çağrılmıyor.

⚠ İkisi aynı iş değil: çekmece **kaydedilmemiş** bir satır için aday süzüyor; ikinci ekran **kaydedilmiş** eğitime kaç eğitmen ve hangi modla (ilk kabul eden / kabul edenler arasından seçim) teklif gideceğini soruyor. Yani ikincisinin var olma sebebi var — eksik olan **gerekçeli liste**.

**Soru:** ikinci ekran da `PlanningCandidates`'e mi geçsin (gerekçeli liste + teklif ayarları bir arada), yoksa "Eğitmen ata" doğrudan çekmeceyi mi açsın?

### B-5 · Tedarikçi fiyat kaydı hiç çalışmıyordu — DÜZELTİLDİ

`Suppliers/Show.vue` içinde `const send = correcting.value ? router.put : router.post;` — metot router'dan **koparılınca** `this` kayboluyor ve Inertia kendi içinde `this.visit`'e uzanıp `undefined` buluyor. Konsol: *"Cannot read properties of undefined (reading 'visit')"*.

⛔ Sonuç: **her tedarikçi fiyat girişi ve her düzeltme sessizce düşüyordu** — panel açık kalıyor, tarihçe boş kalıyordu. Çağrı router'ın üzerinde yapılır oldu; düzeltmeden sonra fiyat kaydedildi ve tarihçe çizildi.

⚠ Aynı deseni üründe taradım, başka örneği yok.

### B-6 · Tarih seçici Escape ile kapanmıyor — DÜZELTİLDİ

Planlama ekranında tarih aralığı seçildikten sonra takvim açık kalıyor; Escape kapatmıyor, dışarı tıklamak da kapatmıyor. Altındaki "Aday listesini aç" butonunu örtüyor — tıklamayı takvim yakalıyor.

### B-7 · Seçenek listelerinde Türkçe karakterler eksikti — DÜZELTİLDİ

`OptionListSeeder` kullanıcıya görünen 10 metni aksansız yazıyordu: *"Toplanti / egitim salonu"*, *"Arac kiralama"*, *"Matbaa ve baski"*, *"Diger"*, *"Tedarikci turu"* ve üç açıklama. Bunlar tedarikçi ekranında filtre çipi ve açılır liste olarak görünüyor.

⚠ Sebep arch kuralı değildi — dosyada zaten `arch-allow: turkish-content` işareti var, yani Türkçe karakter yazılabilirmiş. Aynı dosyadaki 38 başka değer doğru yazılmış.

### B-8 · Satıcı çekmecesi yanlış izne bakıyordu — DÜZELTİLDİ

`/yonetim/satiscilar`'da "Düzenle" butonu **yalnız** `assign_permissions`'a bakıyordu. Satış yöneticisi paketi `user.assign_customers` taşıyor, `assign_permissions` taşımıyor — yani **müşteri atayan tek kontrol, işi bu olan tek rolden gizliydi.** Hiçbir satıcının müşterisi olmamasının sebebi de buydu.

Buton artık dördünden herhangi biri için açılıyor; **yetkiler bloğu** kendi izninin arkasında kaldı. Satış yöneticisi çekmeceyi açıyor, müşteri ve tavan alanlarını görüyor, yetki bloğunu görmüyor.

### B-9 · Kayıt 403 — ÇÖZÜLDÜ *(Sets: paket eksik)*

Sets: *"(a) doğru olan. Operasyon direktörünün de bunlara ihtiyacı var."*

Satış yöneticisi paketine **`user.update.group`** eklendi — satıcıyı yönetmek (müşteri atamak, tavan koymak, hedef vermek) o kişinin kaydını güncellemektir. ⚠ `.all` değil `.group`: yönettiği ekip kadar. Operasyon direktörü ölçüldü, `user.update.all` dahil altısını da zaten taşıyor; eksiği yoktu.

⛔ Ama izin eklendikten sonra da 403 sürdü ve audit sebebi yazdı: **`ability: user.assign_permissions`**. `updateSeller` o eylemi **koşulsuz** çağırıyordu — yani müşteri atayan bir yöneticiyi, formunun hiç sormadığı bir karar reddediyordu. Artık yalnız çekmece sorduğunda çağrılıyor.

### B-11 · Görünmeyen alan yüzünden sessiz 422 — DÜZELTİLDİ

`abilities` alanı `present` (zorunlu) işaretliydi. Yetkiler bloğu gizli olduğunda istemci alanı göndermiyor → **422**, ve hata ekranda olmayan bir alana ait olduğu için **hiçbir yerde görünmüyordu**: modal sessizce açık kalıyordu.

Kural artık aktöre bağlı: yetki verebilen için `present` (kutuları boşaltmak hâlâ bir karar), veremeyen için `sometimes`.

Çekmece açıldı, iki müşteri seçildi, "Kaydet" → **403**.

`updateSeller` metodunun kapısı: `authorize('user.view')` **ve** `authorize('update', $subject)`. Satış yöneticisi paketinde `user.update.*` yok.

⚠ Metodun kendi yorumu bunun tersini söylüyor: *"Dört ayrı karar, arkalarında dört ayrı eylem... her biri kendi kuralının sahibi olan eylemden geçer, **hiçbiri burada yetkilendirilmez**."* Ama metot `update` istiyor.

Dört eylemin kendi kapıları farklı:

| Eylem | Kendi kapısı |
|---|---|
| `AssignCustomersToUser` | `user.assign_customers` |
| `SetSellerDiscountLimit` | `update` (kişi kaydı) |
| `PlaceOfficeUser` | `assignPermissions` |
| `AssignOfficePermissions` | kendi ailesi |

**Soru:** (a) satış yöneticisi paketine `user.update.group` eklensin mi — rolün işi zaten satıcıyı yönetmek, C4 "tavanı değiştir" de bunu gerektiriyor; yoksa (b) ekran karar başına yetkilendirsin, paket olduğu gibi kalsın mı?

### B-3 · Backoffice gerekliliğin okunan alanını yazmıyor — DÜZELTİLDİ

Form artık `acceptedDocumentTypes` ilişkisini yazıyor: **çoklu seçim** (md.7/2 bir eşik, birden çok belge geçer) ve belge istendiğinde **zorunlu**. Backoffice'ten açılan gereklilik artık ölü kapı bırakmıyor.

### B-4 · İki farklı aday ekranı — ÇÖZÜLDÜ

Sets: *"burada çekmece kullanmak daha mantıklı."* `/egitimler/{id}/adaylar` artık `PlanningCandidates` + `CandidatePicker` kullanıyor, `inline`. Ekranda: UYGUN (1) + gerekçeli UYGUN OLMAYANLAR (5). "Kaç eğitmen / atama modu" üstte kaldı.

⚠ Ekranın eski notu *"uygun olmayan burada YOK — K-1"* diyordu; Sets bunu **31.07.2026'da revize etmiş** ve revizyon yalnız `PlanningCandidates`'e yazılmış. İki koruma da yerinde: gerekçeyi yalnız `instructor.view` olan görür, uygun olmayan seçilemez.

### B-10 · `office_user.statuses.invited` sözlükte yoktu — DÜZELTİLDİ

Enum dört durum taşıyor, sözlükte üçü vardı. **17 hesap** bu durumda ve satışçılar ekranında ham anahtar görünüyordu.

### B-3-eski · Backoffice gerekliliğin okunan alanını yazmıyor — B-3 ile DÜZELTİLDİ *(bu kayıt aynı bulgunun ilk yazımıdır)*

`InstructorRequirementResource` formunda **tek** `Select::make('document_type_id')` var: `instructor_requirements.document_type_id` kolonunu yazıyor.

⛔ Yetkinlik kuralı o kolonu **hiç okumuyor.** `InstructorCompetence`, `RequirementMap`, `InstructorFile`, `ApprovalEvidence` ve `MyCockpit` — beşi de `instructor_requirement_document_type` **pivot**'unu okuyor. Pivot'u yazan tek şey **seeder**.

Sonuçları:
1. Backoffice'ten **yeni gereklilik** açılırsa pivot boş kalır → o gerekliliği taşıyan her eğitim için **hiçbir eğitmen uygun olmaz**, aday listesi sebepsiz boş görünür.
2. Mevcut bir gerekliliğin belge tipi backoffice'ten **değiştirilirse** pivot eskisinde kalır → kural sessizce eski belgeye bakar.
3. Alan `->required()` değil; belge isteyip tip seçmeden kaydedilebiliyor.

⚠ Seeder'ın kendi notu ilişkinin **çoktan-çoğa olması gerektiğini** söylüyor: *"Y-2016 md.7/2 bir EŞİKTİR: uzakyol kaptanı ehliyeti de birinci zabit ehliyeti de eşiği geçer, dolayısıyla ilişki 'gereklilik → kabul edilen tipler'dir ve herhangi biri yeter."* Formdaki tek seçimli kontrol bu tarifle çelişiyor.

---

## Tur kapanışı — 08.08.2026 akşamı

### B-47 · Onaylı masraf geri alınamıyordu — DÜZELTİLDİ *(Sets kararı)*

Sets: *"ilk yönetici onayladı, üst yönetici gördü ve reddetti; onay iptali yapılabilmesi gerekir eğer ödemesi yapılmamışsa. İnsan hatası olasılığını 0'a indirerek sistem kuramayız."*

Veritabanı tetikleyicisi kaldırılmadı, **daraltıldı**: `approved → submitted` serbest, doğrudan `approved → rejected` hâlâ reddediliyor. Yeniden açma iki şeyi birlikte yapıyor — kaydı kuyruğa döndürüyor ve eğitimin maliyet kalemindeki gerçekleşen tutarı geri sarıyor. `instructor_payment_allocations`'ta satır varsa (yani ödendiyse) reddediliyor: çıkmış para, onu doğuran talep düzenlenerek geri alınmaz.

### B-48 · Yeniden açılan karar geçmişte hâlâ "onaylandı" görünüyordu — DÜZELTİLDİ

Tarayıcıda çıktı: masraf kuyruğa döndükten sonra karar geçmişi satırı hâlâ *onaylandı* diyordu ve **yeniden aç** butonu duruyordu — "yeniden açılacak bir şey yok" reddine açılan bir kapı. Geçmiş iz kaydını okur (doğrusu budur), ama *yeniden sorulabilir mi* sorusu kaydın bugünkü hâline aittir. Kural `DecisionState`'e alındı; satır artık buton yerine **"yeniden açıldı"** rozetini gösteriyor.

### B-49 · Eğitim günü UTC'de başlıyordu — DÜZELTİLDİ

`09:00` bir **duvar saati** değeri ve üç yer onu UTC'de kuruyordu: üretilen her program üç saat geç yazılıyordu (ekranda 12:00), yoklama penceresi de dersten üç saat sonra açılıyordu. Saat artık tek erişimciden (`Settings::trainingDayBeginsOn`) geliyor; saklama UTC olarak kaldı.

### B-50 · Satır içi aday listesinde ölü "Kapat" düğmesi — DÜZELTİLDİ

Çekmece başlığındaki kapatma zaten `inline` modda kalkıyordu, alt şerittekiyse kalmıyordu — atama ekranında hiçbir şeyin dinlemediği bir düğme çiziliyordu.

### EGT-75 · Test yalıtımı — bir kat yukarısı da kapatıldı

Bir test dosyasının **başka bir test dosyasında tanımlı** yardımcıyı çağırması artık arch testiyle reddediliyor: `--filter` ile geçiyor, grup olarak koşulduğunda "call to undefined function" ile ölüyordu. Altı çağrı vardı; yardımcılar `tests/Helpers/Yetki.php` ve `tests/Pest.php`'ye taşındı.

⚠ Aynı taramada ikinci bir artık çıktı: oturum testleri, bugün kaldırılan JSON ucuna (`/oturumlar`) soruyordu ve **404'ü** yanıt sanıyordu. Artık güvenlik ekranını ve katılımcı alanını yokluyorlar.
