# 3E Eğitim — Proje Haritası

> **Bu doküman karar taşımaz, karara götürür.**
>
> Bir kural iki yerde yazılırsa biri güncellenir, diğeri kalır ve o gün hangisinin doğru olduğunu kimse bilemez. Bu yüzden buradaki her satır ya **tek cümlelik özet + kaynağın adresi**dir, ya da — kaynağı henüz yoksa — kararın **tam metni**dir. İkincisi geçicidir: karar bir spec'e veya AD'ye yazıldığı anda buradaki metin adrese dönüşür.
>
> **Bakım kuralı:** bir karar alındığında **aynı turda** buraya işlenir. Kaydedilmemiş karar, bir sonraki oturumda yeniden sorulur — bu dokümanın var olma sebebi tam olarak budur.

**Son güncelleme:** 29.07.2026 · CI: #811 ✓

---

## 1. Nasıl kullanılır

| Soru | Önce nereye bak |
|---|---|
| "Bu iş kuralı ne?" | Alan bölümü → kararlar tablosu → kaynak |
| "Bu daha önce konuşuldu mu?" | Alan bölümü + §8 açık sorular |
| "Bu tabloyu kim kullanıyor?" | Alan bölümü → veri satırı |
| "Bu ekran var mı?" | Alan bölümü → durum satırı |
| "Sırada ne var?" | §2 genel durum |
| "Bu neden böyle yapıldı?" | Kaynak dosya — gerekçe orada yazılı |

---

## 2. Genel durum

**Faz 0 — altyapı:** tamamlandı (EGT-1…EGT-19, EGT-30). PostgreSQL 18 + TimescaleDB, Redis Sentinel, Postmark, Sentry, nginx, TeamCity.

**Faz 1 — spec yazımı (EGT-20):** ✅ **sekizin yedisi kapandı** *(09.08.2026)*. Belgesi YAZILAN ikisi: omurga (EGT-21), planlama (EGT-25, v1.1 APPROVED). Plan dışında ayrıca dört spec yazıldı: eğitmen (EGT-57), müşteri portalı, fiyatlama, ofis ön yüz. Kalan beşi (EGT-22, 24, 26, 27, 28) **belgesi yazılmadan kapatıldı** — kapsamları teslim edildi ve üretimde; kodun arkasından yazılan bir spec tarif değil tutanaktır *(Sets kararı)*. ✅ **EGT-23 de kapandı** *(10.08.2026)*: spec turu atlandı, iş doğrudan uygulandı; on iki kalem koda karşı ölçüldü ve açık kalan tek satır — onay kuyruğunda **benden / genel** ayrımı — aynı turda yazıldı. **Faz 1 tamamen kapalı.**

**Faz 2 — omurga implementasyonu (EGT-29):** chunk 1–7 ve 10 tamamlandı. ✅ **Chunk 7 kapandı (07.08.2026, EGT-37)** — 18 kriterin 12'si zaten karşılanıyordu, dört eksik (mesaj sınıfı + stream, Discord, SMS adaptörü) bu turda yazıldı; bir tanesi ise ölçüm hatasıydı (bounce webhook'u vardı, rotanın yolu Türkçe olduğu için grep bulamamıştı). ✅ **Chunk 8 da kapandı (07.08.2026, EGT-35)** — 17 kriterin 9'u karşılanıyordu, altısı yazıldı (`last_active_at`, çalışıyor göstergesi, üç test), ikisi müşteri tarafında Reverb kullanılmadığı için **konusuz**. Açık: yalnız chunk 9 (test altyapısı/rollout, EGT-34).

**Ofis ön yüz turları:** T1 (EGT-54) ve T2 (EGT-55) tamamlandı; T3 (EGT-56) T1'e alındı; T4 (EGT-59, grup ağacı + kullanıcı oluşturma + eğitmen kaydı) tamamlandı.

**Faz 3 — dikey modüller (EGT-38):** ✅ **kapandı** *(09.08.2026)* — planlama, satış/teklif, fatura, müşteri portalı, eğitmen paneli ve katılımcı alanı **üretimde**. *Bu satır 03.08.2026'ya kadar "başlamadı" diyordu; uçtan uca denetim o çelişkiyi bulgu olarak kaydetmişti.*

**Sıradaki iş** *(10.08.2026)*: ⛔ **normal düzene geçildi — bundan sonra ticket üzerinden ilerlenir** *(Sets)*. Yardım merkezi (EGT-88), yenilikler (EGT-89), NDA (EGT-90) ve spec (EGT-23) kapandı; **ilk gerçek migrate deploy'u 10.08'de koşuldu** ve production güncel.

Açık kalanlar: **EGT-72** (finans veri katmanı) · **EGT-74** (kapatma kararı Sets'te) · **EGT-76** (D-6 İYS/SMS) · **EGT-79** (D-16 — kapsamlı iyileştirme gerektiriyor, rafta) · **EGT-92** (audit doğrulama aracı — yapısı planlanacak) · omurga chunk 9 (EGT-34).

⚠ **Denetim raporu kendi anlık görüntüsünden konuşuyor — her kalem koda karşı ölçülmeli.** §5.1'in "hiç yazılmamış yirmi ekranı" ölçüldüğünde **on sekizi zaten yazılmıştı**; açık olan ikisi profil sekmeleri ve içerik paketi okuyucusuydu. Aynısı §13.4'te de çıktı: on sidebar bulgusunun yedisi kapalıydı. Rapor bir *başlangıç noktasıdır*, envanter değil.

**Kalan — üç öbek** *(05.08.2026 itibarıyla ölçüldü)*:

1. ~~**Performans (Kuşak 5).**~~ ✅ **Kapandı (05.08.2026).** İndeksler: indekssiz yabancı anahtar **137 → 0**, RLS `tenant_id` indeksi **0/5 → 9/9** (denetim 96 ve 5 saymıştı; ikisi de büyümüştü, çünkü süreçte indeks ekleyen hiçbir şey yoktu). N+1: **N-1…N-9** iki commit'te, **N-10** kendi turunda — `StaffCompliance` artık sayfayı ve tüm-küme toplamlarını aynı SQL kümesine sorulan altı sabit soruyla okuyor. ⏸ Tek kalan `InstructorWorkload`'ın PHP sayfalaması; kümesi 3E'nin kendi kadrosu olduğu için **kasıtlı bırakıldı**, gerekçesi denetim dosyasında.
2. **Yazılmamış akışlar (§12.1).** Kuşak 3 D-1…D-5 ve D-8…D-12'yi kapattı. **Hiçbir kuşakta olmayanlar:** D-6 (İYS/SMS), ~~D-7~~ ✅ (Reverb — **yalnız ofis**, Sets 05.08.2026), ~~D-13~~ ✅ (3E'nin kendi yetki belgesi — 09.08.2026), D-14 (e-fatura + bağımsız katılımcıya fatura), D-15 (muafiyet motoru), D-16 (IMDG ön koşulu), ~~D-17~~ ✅, ~~D-19~~ ✅, ~~D-20~~ ✅. **§12.2 KAPANDI** *(05.08.2026)* — on bir kalem koda karşı ölçüldü *(05.08.2026, tablo denetim dosyasında)*: **dördü zaten kapalı** (`last_taught_on`, `kums_evidence`, `analytics_usage_daily` — üstelik "saatlik yazılıyor" yanlış, hiçbir şey yazmıyor —, kontenjan teyidinin çözüm yolu). Kalan yedinin **hiçbiri düz "bağla" işi değil**: ikisi ✅ **05.08.2026'da bağlandı** — `record_retention_years` (⛔ imha değil: mevzuat **asgari** saklama koyuyor; eğitim kaydı ekranı *"3 yıl — 15.03.2029'a kadar zorunlu"* diyor, hiçbir şey silinmiyor) ve `exam_retake_*` (⚠ `attempt` kolonu 25.09'da tam bu gün için konmuştu; yan bulgu: `RecordCertificate` ilk sınavı kaybedip ikincisini kazanana belge vermiyordu) —, biri ✅ **05.08.2026'da kapandı** (`kind` — ⛔ "boşluk" değil **ölü uç**: bildirimden sonra katılımcı eklenen eğitim bildirim modelinde **asla başlatılamıyordu**, çünkü `needs_kums_update` bayrağını temizleyecek yol yoktu; `RecordMinistryUpdate` + `RecordMinistryCancellationNotice` yazıldı), ikisi ✅ **05.08.2026'da karara bağlandı**: `named_seats` **kaldırıldı** (Sets: *"20 satıldı diye bir cümle gerçek olmaz, günlük eğitim alır adam"* — üstelik her katılımcı hareketinde bir trigger yazıyordu, okuyan yoktu), `regulation_fallbacks` ise **işlevsel hâle getirildi** — ⛔ zincir artık **uyarı çözer**; TY-2019'a bağlı bir eğitim Y-2016 ve YÖN-2019'un uyarılarını da *"devralınan"* işaretiyle görür. Bu, mevzuat §11 ve AD-27'deki *"otomatik çözümlenen zincir tablosu yoktur"* satırını **tersine çevirir** (Sets: *"güncel çıkan sadece atıf ile bazı değişiklikler içerebiliyor"*); ⚠ kısıt seti geri gelmedi — zincirden hiçbir sayı türetilmez, ikisi ✅ **05.08.2026'da kapandı**: yazışma triyaj ekranı (backoffice, Sets'in kararı) ve organizasyon kontrolleri (ad/taşı/yönetici/kapat — Sets dördünü de istedi).

⛔ **Organizasyon kontrollerini bağlarken iki canlı hata çıktı, ikisi de zaten oradaydı** *(detay denetim dosyasında)*: (1) route model binding tenant scope'tan **önce** koşuyordu → bağlı her portal adresi 500; (2) `PermissionRows` müşteri config'inin iç içe şeklini yanlış okuyordu → *"Array to string conversion"*. İkisi birlikte tasarım `11`'in **yetki matrisini tamamen çalışmaz** hâlde tutuyordu. Ders: *ekran açılmadan "yazıldı" demek, bu iki hatayı aylarca taşımak demek.* ⛔ D-6, D-7 ve D-13…D-16 **kapsam kararı ister**, kod işi değil.
3. ~~**Doküman bakımı (Kuşak 6).**~~ ✅ **Kapandı (07.08.2026).** §12.3'ün altı çelişkisinin altısı kaynağından düzeltildi: KamuSM RFC 3161 (çatı §192 + AD-21), dayatma dili (`mevzuat-kurallari.md` §1/§12 **ve `RegulationSeeder`'ın iki uyarı metni** — zararlı olan sonuncusuydu, formu dolduran onu okur), `PK` → `PR`, duyuru kanalı (çatı §67 artık "yalnız uygulama içi", gerekçesiyle), kişi eşiği (omurga §8 + kriter 9 → `business.usage.minimum_people`), ve bu dosyanın §2'si. Ayrıca 4.5/4.6/4.7/4.8'deki **beş yanlış ✗ işareti** düzeltildi — hepsi yazılmış, harita yazılmamış diyordu.

⛔ **Üç öbeğin üçü de kapandı; sıradaki iş artık denetim değil.** 07.08.2026 akşamı itibarıyla açık olan: **sözleşmeler dikeyi** (EGT-80 site sözleşmeleri/`legal_*` — ✅ müşteri tarafı 09.08.2026'da yazıldı; EGT-82 ticari sözleşmeler — fiziksel olarak ayrı), **kapsam kararı bekleyen beş kalem** (D-6, D-13, D-14, D-15, D-16), ve **katalog tamamlama** (`DescriptionList` 21 sayfa, `ProgressBar` 9 sayfa, §13.2 organizma çıkarma).

**Kuşak 4 — yazılmamış yirmi ekran, role göre gruplanmış** *(Sets: "role göre grupla ve eksiksiz tamamlayarak ilerle")*. Kapananlar: ✅ **eğitmen/saha** ve ✅ **katılımcı** (`80e6c6f`) · ✅ **ofis — planlama** (`6d307d9`): planlama kuyruğu (onaylı tekliften plana giden yol **hiç yoktu**), tedarikçi pasifleştirme (`suppliers.is_active` tablo doğduğu günden beri duruyordu ve **hiçbir şey onu yazmıyordu**), talep hatırlatması, "bu hafta operasyon" kartı · ✅ **ofis — satış/muhasebe** (04.08.2026): **belge şablonları** ekranı ve **faturada iki oluşturma yolu** (matris: `docs/reviews/kapsama-ofis-satis-muhasebe-2026-08-04.md`) · ✅ **ofis — yönetim** (04.08.2026): **karar geçmişi**, **yeni kullanıcı sayfası**, **dikkat tonu kartı** (matris: `docs/reviews/kapsama-ofis-yonetim-2026-08-04.md`).

⛔ **Antet: tablo vardı, yazan yoktu** *(04.08.2026)*. `document_templates` doğduğu günden beri teklif çıktısı ve yoklama listesi tarafından **okunuyordu** ve hiçbir ekran, route ya da action ona satır yazmıyordu — 3E'nin kendi anteti yalnız bir veritabanı istemcisiyle kurulabiliyordu. `/yonetim/belge-sablonlari` yazıldı (`document_template.view` / `.update`, tasarım `37:115`'in dediği gibi **operasyon yöneticisinin**). İki karar: (1) yükleme **sürüm açar, üzerine yazmaz** (AD-11) — "müşteri geçen mart ne aldı" cevaplanabilir kalmalı; (2) **gönderilen teklif kendi antedini dondurur** (`quote_versions.letterhead_template_id`) — yeni antet, müşterinin elindeki PDF'i geriye dönük değiştiremez. Asgari genişlik **ayardır**, sabit değil (`company.letterhead_min_width_px`, kırmızı çizgi 9).

⛔ **Faturada iki oluşturma yolu** *(04.08.2026, tasarım `62:150-151`)*. `POST /muhasebe/faturalar` doğduğu günden beri hem teklifi hem serbest kalemi kabul ediyordu ve tek çağıranı **bir teklifin üzerindeki bir düğmeydi**. Sonuç: haftanın onaylarını sırayla işleyen muhasebeci kendi ekranından fatura kesemiyor, tekliften gelmeyen bir tutar — geç iptal bedeli — **hiç** faturalanamıyordu. İkisi de listenin başlığına eklendi; faturalanmış teklif seçiciden düşer (M-18, bütün faturalanır).

⛔ **Ham `<table>` — "32 sayfa" tek bir iş değildi** *(04.08.2026, denetim §13.3)*. Sayfaları açınca üç ayrı şey çıktı: (1) **kayıt LİSTESİ** — `DataTable`, standardın tamamını taşıyor; (2) **kaydın İÇİNDEKİ tablo** (kalemler, oturumlar, tahsilatlar) — bunlar için `DetailTable` yazıldı: aynı başlık/hücre/kart muamelesi, kimsenin sormadığı soru için mobilya yok (üç satırın üstüne arama kutusu ve "1–3 / 3" koymak); (3) **basılı belge** — A4'e arama kutusu ve sayfalayıcı koymak saçmadır. Üçüncüsü **kalıcı istisna**; `Planning/Calendar` de öyle, çünkü o bir **matris** (eğitmenler aşağı, günler yana, yapışkan ilk kolon) — karta dönüşecek "satır"ı yok. Beş sayfa taşındı, **24 sayfa arch testinde açık liste olarak duruyor**: liste yalnız kısalabilir ve ödenmiş borcu kaydedemez.

⛔ **Kayıtlı görünüm İKİ KEZ yazılmış — birleştirildi** *(05.08.2026)*. `staff_query_views` sorgulama ekranı için 21.09'da yazılmıştı; ben 28.09'da her liste için `table_views`'ı yazdım — **var olup olmadığına bakmadan**. Bir kavram için iki tablo, iki model, iki route ve iki action; birinin diğerinden haberi yok. Genel olan kazandı (`table_views` eskisinin taşıdığı her şeyi artı kataloğun istediği üçünü taşıyor: hangi EKRAN, adın altındaki not, hangisi varsayılan). Var olan satırlar silinmeden **kopyalandı** — bu makinede hiç yoktu, ama olsaydı atacak bir migration doğru değil sadece şanslı olurdu.

⚠ Birleştirirken sildiğim action'ın **kaydetmeden önce ayıklama** yaptığını fark etmedim; test yakaladı. Görünüm yalnız **koşulları** tutar: sayfa numarası koşul değildir (üçüncü sayfada açılan bir görünüm adlandırıldığı listeyi göstermez), boş bir boyut da öyle ("il: hiçbiri" koşul değil). Sıralama tutulur, çünkü "en eski tarih önce" adlandırılan şeyin parçasıdır. Ayrıca başkasının görünümünü silme denemesi artık **404** veriyor — eskiden sessizce yönlendiriyordu, yani çağırana işe yaradığını söylüyordu.

⛔ **Tekrar eden sorgu olan beş ekrana kayıtlı görünüm bağlandı** *(Sets, 05.08.2026: "sadece tespit ettiğin, tekrar eden sorgu olan yerlere koy")*: sorgulama, faturalar, eğitimler, müşteriler, eğitmenler. Kalan sekizine **bilerek konmadı** — az satırlı, tek boyutlu listelerde "görünüm kaydet" karşılığı olmayan bir kontroldür.

⛔ **"Hepsi düzeldi mi?" — hayır, dört eksik daha vardı** *(05.08.2026)*. Bileşen 18/18 olmuştu ama **ekranlar sunduğu şeyi kullanmıyordu**; sorulunca hafızadan değil ölçerek bakıldı: (1) **sunucu sayfalamalı tabloda sıralama yoktu** ve eklenseydi **yalan söylerdi** — tarayıcıda 25 satır sıralamak o sayfayı sıralar, en üste ait satır yedinci sayfada olabilir; sıralama sunucuya geçti (`sirala`/`tersine` adreste, yani sıralanmış sonuç meslektaşa gönderilebiliyor) ve `StaffCompliance` kolon adını **izin listesine** karşı doğruluyor. (2) **Satır aksiyonu `⋮` hiçbir ekranda yoktu** — `RowMenu` yazıldı; dört düğme her satırda veriyle yarışır, işaret tektir. (3) `Portal/Trainings` **kendi filtre şeridini** taşıyordu, yani tablonun araması ile sayfanın filtresi aynı liste hakkında iki ayrı soruya cevap veriyordu. (4) Kayıtlı görünüm tek ekrana bağlıydı.

⛔ **Ve `⋮`'yi eklerken bir gerileme yarattım, turu yakaladı** *(05.08.2026)*: "Aç"ı menüye taşıyınca **satır tıklanamaz oldu** — liste vardı, içine girilemiyordu. Doğrusu: satırın **adı** kaydı açar (bağlantı), `⋮` geri kalanını taşır. Menü ikincil eylemler içindir; satırın ne OLDUĞUNU açmak birincil eylemdir.

⛔ **Toolbar tasarımın ölçüleriyle değil, benim tahminimle yazılmış** *(Sets, 04.08.2026, ekran görüntüsüyle)*: *"search alakasız? butonlar alakasız? neyi hizaladın sen?"* Tasarımcı toolbar'ı **tek satır, ~30px yüksekliğinde kompakt bir kontrol şeridi** çiziyor — 240px sabit gri arama alanı, `padding:5px 10px` düğmeler, `flex-shrink:0` yoğunluk anahtarı. Ben oraya **44px'lik FORM kontrolleri** koymuşum: şerit iki katı yükseklikte, her etiket iki satıra bölünüyor, satır üçe sarıyor. Düzeltme madde madde değil, **ölçüleri dosyadan alarak** yapıldı; `ToolbarSearch` atomu yazıldı (form alanı ≠ toolbar araması: biri beyaz çerçeveli `min-h-12`, diğeri 240px gri hap). Tarayıcıda ölçüldü: arama 240px, düğmeler 26px, hepsi aynı y'de. ⚠ 1024'te arama 178px'e sıkışıyordu — sabit genişlik baskı altında bükülüyorsa sabit değildir; `shrink-0` eklendi ve test bunu ölçüyor.

⛔ **Kayıtlı görünümler yazıldı** *(04.08.2026)* — tasarım `02:113-119`'un son eksiği. `table_views`: kişiye ve EKRANA özel, filtreler JSON (boyutun anlamı ekranın işidir, her yeni filtre migration olmamalı), kişi+ekran başına tek ad ve tek varsayılan veritabanı indeksiyle. ⛔ **Yetki gerektirmiyor ve bu eksiklik değil**: görünüm *satır* değil *filtre* tutar — uygulandığında yine aynı sorgu, aynı policy, aynı satır güvenliği çalışır, yani görmeye hakkı olmayan birine boş liste gelir. Korunan şey bir yer imidir. Sahiplik kontrol ediliyor ve başkasının görünümü **404** veriyor: bir meslektaşın yer imi olup olmadığı bu adresin cevapladığı bir soru değil.

⛔ **DataTable tasarımdan değil, denetimin ÖZETİNDEN yazılmış** *(Sets, 04.08.2026)*: *"tasarımcının verdiği hali değil ki bu?"* Dosya açılıp satır satır okunduğunda 18 öğenin **7'si ✓, 4'ü ⚠, 7'si ❌** çıktı. En ciddisi filtre modeliydi: tasarım `02:37` **üst üste binen, tek tek kaldırılabilir koşullar** çiziyor (`İl: Kocaeli ✕` + `Durum: … +2 ✕`), ben **tek seçimli** bir çip şeridi yapmıştım — "Kocaeli VE (dolmuş VEYA dolacak)" sorusu o modelle sorulamaz, ki sorgulama ekranının var olma sebebi budur. Eksik olan diğerleri: `＋ Filtre`, `Görünümü kaydet`, **satır aksiyonu ⋮**, bulk bar'ın rengi (mavi değil gri yapmışım), boş durum ikonu ve boş durumun **hangi filtrenin** boşalttığını söylemesi, mobilde uzun basışla seçim ve alttan sabit toplu eylem. **Hepsi yazıldı**; kapsama matrisi 18/18. Çağıran 14 sayfa düz `filters` listesini vermeye devam ediyor — bileşen onu tek bir boyuta çeviriyor, yani hiçbir sayfa düzenlenmeden kaldırılabilir çipleri kazandı.

⛔ **Ders: "render ediliyor" ≠ "çalışıyor"** *(04.08.2026)*. Tarayıcı turu bu bileşeni taşıyan her sayfayı yeşil veriyordu, çünkü tur **açılıyor mu** diye bakıyor. Hiçbir şeyi süzmeyen bir arama kutusu, hiçbir şeyi sıralamayan bir başlık ve hiçbir şeyi kaldırmayan bir çip kusursuz render eder. `tests/Browser/data-table.spec.js` yazıldı: arama satırları daraltıyor mu, başlık gerçekten sıralıyor ve `aria-sort` basıyor mu, çip beliriyor ve ✕'i geri alıyor mu, kolon seçici kolonu düşürüyor mu, yoğunluk satır yüksekliğini değiştiriyor mu, 768px altında tablo gerçekten karta dönüyor ve sayfa yana kaymıyor mu.

⛔ **DataTable `02` standardına çıktı, sorgulama ekranı ona taşındı** *(04.08.2026, denetim §13.3)*. Katalog tablosu standardın yarısını taşıyordu: sticky header, sıralama, arama, tek seçimli filtre çipleri, iki oklu sayfalama. Eksik olanlar **çoklu seçim + toplu işlem şeridi, yoğunluk (Kompakt/Rahat), kolon seçici, footer toplamı, numaralı sayfalama** ve **sunucu tarafı sayfalama** idi. Hepsi yazıldı — ve yalnız yazılmadı: tasarım `02:31`'in tablo standardını **üzerinde çizdiği** ekran olan **planlama sorgulaması** ona taşındı. O ekran kendi checkbox kolonunu, kendi toplu şeridini ve kendi iki oklu sayfalayıcısını elle yazmıştı. ⛔ **Tablo SEÇER, ekran EYLER**: "eğitim planına ekle"nin ne demek olduğunu bilen bir tablo, her yeni toplu eylemde düzenlenmesi gereken bir tablodur. ⚠ Footer toplamı **tüm sonucun**, sayfanın değil — ekranın açılma sebebi olan cümle budur; önündeki 25 satırı toplamak kimsenin sormadığı soruyu cevaplar. Başlık kutusu `Checkbox` atomunun **indeterminate** hâlini kullanıyor: üç satır seçiliyken "hiçbiri" diyen bir başlık, o atomun yazılma sebebiydi.

⛔ **Elle yazılan etiket/değer blokları — bir desen değil ÜÇ desendi** *(04.08.2026, denetim §13.2)*. Denetim "24 sayfa elle `<dl>` yazıyor, `DescriptionList`'e taşınmalı" diyordu; açıp bakınca tek bir desen değil **üç ayrı desen** çıktı ve katalogda yalnız birincisi vardı: (1) **description list** — etiket değerin YANINDA, sabit 120px kolon (tasarım `02:248`, vardı); (2) **fact grid** — başlık figürün ÜSTÜNDE, üç-dört sütunluk şerit (tasarım `62:207`, `66:326` — **13 kez çizilmiş, 0 kez yazılmış**); (3) **summary rows** — etiket solda, tutar SAĞ kenarda (tasarım `37:77` toplamlar bloğu — **8 kez çizilmiş, 0 kez yazılmış**). Sonuç: her sayfa kendi boşluğunu, kendi başlık boyutunu ve kendi sütun sayısını seçmişti; tasarımların **aynı** çizdiği iki panel birbirini tutmuyordu. `FactGrid` ve `SummaryRows` yazıldı, 14 blok taşındı. ⚠ Kalan 14 dosya arch testinde **açık liste** olarak duruyor — liste yalnızca **kısalabilir**, ve bir isim `<dl>` yazmayı bıraktığında listeden **düşmek zorunda** (ikinci yön), yoksa ödenmiş bir borcu kaydeden bir liste olur.

⛔ **Menü iki seviyeye çıktı, gruplar tavana indi** *(04.08.2026, denetim §13.4 S-1/S-2/S-5/S-6/S-7)*. HANDOFF kural 12 ve tasarım `05:50` grup başına **≤5 kalem** diyor; `planning` ve `management` **dokuzar** taşıyordu. Tasarım `05:44-48`'in çizdiği **ikinci seviye hiç yazılmamıştı** — o yüzden kural tutulamıyordu. Ebeveyn bir **hedeftir, klasör değil**: "Eğitimler" listeyi açar, çocukları aynı sorunun tarih ve kişi hâlleridir. ⛔ **Ebeveyni kapalı olan çocuk üst seviyeye çıkar, kaybolmaz** — yalnız `supplier.view` tutan biri tedarikçi ekranına ulaşmalı, ebeveyni göremediği için menüden düşmemeli. `management` dokuzdan dörde inerken **yeni bir grup** açıldı (`AYARLAR VE KAYITLAR`): anket sonucu bir kullanıcı türü, sistem günlüğü bir antet türü değildir — bunlar bir *grup*tur, bir *ebeveyn* değil. Ayrıca: eğitmen gündemi tasarım `40:34`'ün dediği gibi GENEL'e taşındı (S-5) ve "3" sayacı takvimden **Eğitimlerim**'e geçti (S-6) — sayaç atamaları sayıyor, günleri değil. Üç arch testi kuralı kilitliyor.

⛔ **Çelişen ikinci yazı tipi silindi** *(04.08.2026, §13.5)*. `resources/css/app.css` Laravel'in varsayılanı `Instrument Sans`'ı, token seti `Archivo`'yu tanımlıyordu. **Hiçbir shell app.css'i yüklemiyordu** — üç panel kendi paketini adlandırıyor — yani çelişen kopya kimsenin fark etmediği kopyaydı. Dosya ve boş `app.js` build girdilerinden çıkarıldı; arch testi artık `--font-sans`'ın **yalnız `tokens.css`'te** tanımlı olmasını şart koşuyor.

⛔ **Karar geçmişi — verilen karar hiçbir yerde durmuyordu** *(04.08.2026, tasarım `66:382-392`)*. Bir onay ya da ret alındığı anda kuyruktan siliniyor ve başka hiçbir ekranda görünmüyordu: *"bunu onaylamış mıydım?"* sorusunun cevabı sistem günlüğüydü — denetim aracı, çalışma aracı değil. Kuyruğa "bugün sonuçlananlar" kartı, arkasına `/onaylar/gecmis` sayfası eklendi. ⚠ **Denetim izinden okunuyor**: beş onay türü dokuz ayrı action tarafından beş ayrı tabloya yazılıyor ve hepsinin ortak yaptığı tek şey aynı transaction'da denetim satırı yazmak (AD-06); tabloları birleştiren bir sorgu, yeni tür eklenince güncellenmesi unutulacak altıncı yer olurdu. `training.cancelled` bir **onaydır** ve adı `_approved` ile bitmez — eşleme bu yüzden açık yazıldı.

⛔ **Yeni kullanıcı artık kendi sayfası** *(04.08.2026, tasarım `64:341-388`)*. Drawer kimlik ve grup yerleşimini topluyor ve duruyordu; **üçüncü bir adım olduğunu söyleyen hiçbir şey yoktu**, dolayısıyla davet edilen hesap giriş yapıp hiçbir sayfa göremiyor ve kimse sebebini bilmiyordu. Sayfa üç şeyi ekliyor: adım göstergesi, *"hesap sıfır yetkiyle doğar"* uyarısı ve üç başlangıç kartı (şablon · kişiden kopyala · gruba varsayılan — sonuncusu tasarımın **karşı çıktığı** yol, gerekçesiyle çiziliyor). Seçilen şablon `?sablon=` ile yetki ekranına taşınıyor ve orada **işaretli açılıyor ama yine elle kaydediliyor** — tasarımın kendi alt cümlesi bu. `CreateUserForm.vue` silindi: aynı formun iki kopyası tam olarak kaçınılan şeydi.

⛔ **Sertifikayı 3E basmaz ve yayınlamaz** *(Sets, 04.08.2026)*: *"Biz sertifika basmayacağız. İleride kendi eğitimlerimiz olursa onların sertifikasını basacağız, ama bakanlık mevzuatlı eğitimlerin sertifikasını bakanlık basıyor. Bunları da yayınlamıyoruz: olur da bakanlık iptal eder, bizde görüp geçerli zannetmesin, her zaman gidip bakanlıktan baksın diye."* Üç sonuç: antet ekranındaki **Sertifika rozeti kaldırıldı** (tasarım `37:124` çiziyordu — hiçbir şeyin üretmediği bir belgeyi vaat ediyordu); sorumluluk metni **iptal ihtimalini** söyleyecek şekilde güçlendirildi (eskisi yalnız "Bakanlık düzenler, biz saklamıyoruz" diyordu); **katılımcı panelinde metin hiç yoktu** — ekran "Geçerli" rozeti basıp yanına hiçbir şey yazmıyordu. Rozet, sistemin kendi kopyasına güvenmeyi öğretiyordu; asıl kayıt Bakanlıktadır.

⛔ **Gerçek kişiye fatura — yetkinlik sağlandı** *(Sets, 04.08.2026)*: *"Firmalar tüzel ya da gerçek kişi olabiliyor, haliyle gerçek kişiyi de müşteri olarak ekleyip fatura kesebilirler. 'Kesebilirler' diyor, kesmek zorundalar demiyor."* `legal_type` tablo doğduğundan beri `natural_person` kabul ediyor ve form da sunuyordu; eksik olan **kimlik alanıydı** — tek kimlik sütunları vergi dairesi/numarasıydı ve serbest çalışan bir kişide ikisi de yok. `customers.national_id` eklendi: şifreli (AD-16, `instructors.national_id` deseni), modelde `$hidden`, formda yalnız gerçek kişide ve `SensitiveInput` ile, **düzenlemede boş = değişmedi** (aksi hâlde ilk ad düzeltmesinde numara silinirdi). Teklif çıktısı gerçek kişide "T.C. Kimlik No" basıyor — yanlış etiket, numarayı hiç basmamakla aynı hatadır. **Zorunlu değil.**

⛔ **Demo veri iki yerde kırıktı, tur buldu** *(04.08.2026)*. (1) Kuşak 3'ün tamamlanma kapıları (sınav evrakı + oturum başına video) demo setine hiç yazılmamıştı — temiz kurulan bir veritabanında **tek eğitim kapanmıyordu**. (2) Hiç demo tedarikçi yoktu: `/tedarikciler` boş liste çiziyor, arkasındaki detay ekranı — fiyat tarihçesi, anlaşmalar, evraklar, pasifleştirme — turda **hiç açılamıyordu**. `DemoSuppliersSeeder` yazıldı (dört tedarikçi, üç tür, iki il, aynı hizmete üç fiyat, bir tanesi kapalı).

⛔ **Kuşak 3'te alınan kararlar** *(Sets, 04.08.2026)*: (1) **video kaydı oturum başına** — Zoom bir toplantıyı kaydeder, iki günlük eğitim iki toplantıdır; (2) **yenilemede önceki sertifika uyarır, engellemez** — AD-27 çizgisi, kâğıt klasörde olabilir ve reddeden sistem etrafından dolaşılan sistemdir; (3) **`video_kaydi_zorunlu` alanı `sinav_zorunlu` yanına eklendi** (Sets'in önerisi) — videosuz ya da sınavsız bir eğitim ileride sistemi kilitlemesin diye. (4) Eğitim evrakları **teslimde** istenir: imzalı yoklama gün kapanışında zaten zorunlu, sınav evrakı sınav yapıldıktan sonra var olabilir, eğitim materyalleri ise `TrainingMaterial` olarak zaten tanıma bağlı ve eğitmen atandığında görüyor.

⛔ **Kuşak 1'de alınan karar — gün bazlı teklif kalemi koltuk tutmaz** *(Sets, 04.08.2026)*: *"Gün bazlı verilen teklifte kişi sayısı bizi sadece iki konuda ilgilendirir: bakanlığın asgari ve azami limiti. Bunun dışındaki kısım müşteriye kalmış."* Satılan şey bir günlük eğitimdir; odaya kaç kişi oturacağı firmanın kararıdır ve onu yalnız mevzuat sınırlar — faturalanan tutar değil. Bunun üzerine `named_seats <= confirmed_seats` CHECK kısıtı **kaldırıldı**: var olmayan bir kuralı kodluyordu ve `named_seats` yazılmaya başladığı anda, parasını tam ödemiş bir firmanın ilk personelini eklerken patlayacaktı. Duran sınır `confirmed_seats <= capacity`, çünkü `capacity` mevzuatın azamisini taşır.

### ⛔ Local prod ayakta — ve kurulum kodu ilk kez koştu *(09.08.2026, EGT-71)*

`10.31.1.210` **local production** olarak kuruldu: `egitim.devprod.sets.fyi` + `panel.` · `ofis.` · `backoffice.` · `ws.infra.` (Cloudflare gri bulut, Let's Encrypt DNS-01, nginx + php-fpm 8.5 + PostgreSQL 18 + PgBouncer + Redis Sentinel + Horizon + Reverb). Tam seeder ve demo veri yüklü; ekip buradan gezebilir.

⛔ **Üç makine üç şeydir ve karıştırılmaz** *(Sets)*: 1.25 geliştirme · **1.210 local prod** · Hetzner production. Prod host ailesi **`egitim.3egrp.com`**'dur *(Sets, 09.08.2026)*; `akademi.3egrp.com` akademinin tanıtım sitesi olarak ve **posta gönderim domain'i** olarak kalır (DKIM/SPF o adla doğrulanmış).

⚠ **Kurulumun kendisi bir denetimdi.** Deploy script'i, compose prod override'ları, pgbouncer prod yapılandırması ve systemd birimleri yazılıydı; **hiçbiri hiç koşmamıştı** ve yedi ayrı yerde kırıldılar (liste ve gerekçeleri: `docs/infra/ortamlar.md`). En pahalısı görünürde ilgisizdi: PDO havuz arkasında düz metin `DEALLOCATE` gönderiyor, PgBouncer onu çevirmiyor ve **açık bir transaction iptal oluyordu** — hata *"müşteri kaydedilemedi"* gibi okunuyordu. Havuzdan geçen bağlantılar artık sunucu tarafı hazır sorgu kullanmıyor.

✅ **Production da ayakta** *(09.08.2026)*: `egitim.3egrp.com` + dört alt alan, Hetzner'da, aynı yığın. Veritabanı **temiz** — demo veri yok, yalnız referans veri (mevzuat, seçenek listeleri, 32 eğitim tanımı, bildirim kuralları, yasal metinler) ve izin sözlüğü.

⛔ **İlk hesap `php artisan backoffice:add-super-admin` ile açılır ve parola SORULUR.** Hesap üreten her seeder `local` dışında koşmayı reddediyor; bu doğru ve değişmiyor. Sonucu ilk kurulumda görüldü — paneller ayakta, kapı çalışıyor, açacak kimse yok — ve komut o boşluğu bilerek, tek seferlik bir işle kapatıyor.

⚠ **Sırada:** ekip incelemesi (1.210 üzerinden) ve yardım merkezi (`design/Yardim Merkezi.dc.html`) — ikisi paralel *(Sets, 09.08.2026)*.

### ⛔ Alan adı topolojisi ve landing siteleri — ayrı repo, ayrı barındırma *(Sets, 08.08.2026)*

**Kurumsal** landing sayfaları bu repoya girmez: `git@github.com:Sets/3egroup.git`, klasör `~/Projects/3egroup`, kendi oturumu ve kendi `CLAUDE.md`'si var — orada yalnız `3egrp.com` ve `akademi.3egrp.com` vardır.

⛔ **Ürün landing'i (`egitim.3egrp.com`) BU repodadır ve Laravel'dir** *(Sets, 09.08.2026 — çelişki düzeltmesi)*. Haritanın önceki hâli onu da Cloudflare'e yazıyordu; `3egroup/CLAUDE.md` ve `routes/web.php`'nin placeholder notu baştan beri burayı işaret ediyordu. Public host'un `/` adresi tasarımın kendisidir (`design/L01`), yasal metinler `docs/legal/`'ten okunur.

⛔ **Yardım merkezi landing'e GELMEZ** *(Sets, 10.08.2026 — ikinci düzeltme)*. Tasarım `L01` üç yerden `/yardim`'a bağlanıyordu ve harita da "buraya gelecek" diyordu; ikisi de yanlıştı. Yardım Merkezi **ofis panelinin içindeki** bir ekrandır — planlamayı, teklifi, onayları 3E'nin kendi personeline anlatır ve onların girişinin arkasındadır. Herkese açık bir sayfadan oraya bağlanmak, ziyaretçiyi açamayacağı bir kapıya göndermektir. Dört bağlantı da kaldırıldı, öksüz kalan beş çeviri anahtarıyla birlikte. Gerekçe teknik değil bağlamsal — bu projenin kuralları (RLS, `SET LOCAL`, mevzuat kaydı, audit, test grupları) statik bir sitede karşılıksız; bir metin değişikliği için o yükü taşımak anlamsız, tersine landing'in kuralları (marka dili, KVKK, gerçek veriyle ekran görüntüsü yasağı) burada karşılıksız.

**Topoloji** — üç seviye, paneller kardeş, bir panel başka bir panelin altına asılmaz:

| Rol | Host | Nerede |
|---|---|---|
| Grup landing | `3egrp.com` | Cloudflare |
| Akademi (şirket) landing | `akademi.3egrp.com` | Cloudflare |
| Ürün landing + yardım | `egitim.3egrp.com` | **Hetzner — bu repo** |
| Müşteri paneli | `panel.egitim.3egrp.com` | Hetzner |
| Ofis | `ofis.egitim.3egrp.com` | Hetzner |
| Backoffice | `backoffice.egitim.3egrp.com` | Hetzner |
| Postmark uçları | `posta.egitim.3egrp.com` | Hetzner |

⛔ **Ürünler arası yazışma kararı — AD-29** *(Sets, 16.08.2026)*: gözetim yazılımı da teklif gönderecek ve **aynı takip sistemini** kullanacak. Karar *(16.08.2026'da iki kez revize edildi)*: gelen posta adresi **tek** (`teklif.3egrp.com`), ürün ayrımı **referans kodundaki önekten**, postayı **eğitim alır ve saklar**, başka ürüne ait mektubu **o ürün çeker** ve işlediğini ayrı bir çağrıyla onaylar. Ölçümler: Postmark'ta bir gelen akışta yalnız TEK kanca adresi olabilir (yani «her ürün kendi etiketine baksın» mümkün değil) ve başarısız kancayı ~10 saat yeniden dener (yani ayrı bir geçit servisinin gerekçesi yoktu). İtme yerine çekme, çünkü itmede eğitim hedefin sağlığından sorumlu olur; çekmede tek yükümlülüğü ucu açık tutmaktır. ⚠ Bugünkü önekler ürünü söylemiyor (`TK`, `EG`, `FT`…); şema **ikinci ürün ilk teklifini göndermeden önce** kararlaştırılmalı. **Uç yazıldı (EGT-168, 16.08.2026):** `/api/posta/bekleyenler`, `/api/posta/{mektup}/ek/{ek}`, `/api/posta/{mektup}/islendi` — hepsi HMAC imzalı istek arkasında, her sorgu imzanın çözdüğü ürünle daraltılıyor, mektup onaylanana kadar bekliyor kalıyor. ⛔ Tüketici tarafını yazan **`docs/reference/urunler-arasi-posta-api.md`**'yi okur. Geriye kalan: önek şeması + gözetim tarafındaki çekme döngüsü (GZT-1).

`egitim` seviyesi ürünün landing'i orada oturduğu için var — yoksa `akademi` zaten eğitim demek ve seviye bilgi taşımazdı. **Derinlik burada durur:** ileride tenant başına alt alan istenirse dördüncü seviyeye inilmez, tenant path olur.

⚠ **Hostlar passkey relying party id'sidir** (`config/app.php:95`). Ad değişirse o ad altında kayıtlı her passkey çöp olur — karar bugün, yalnız dev hostları varken alındı.

⛔ **`routes/web.php`'deki placeholder landing emekliye ayrılır**; prodüksiyonda `public` host'ta yalnız Postmark inbound/feedback uçları kalır.

**Ürün landing'inin kapsamı** *(Sets, 08.08.2026)*: okuyucusu **müşteri**, ofis değil. Panelin ne yaptığı, ekranları, SSS. Üç sınır: (1) backoffice ve ofis ekranları oraya **girmez** — iç işleyiş ve fiyatlama mantığı dışarı açılmaz; (2) ekran görüntüleri **gerçek tenant verisinden alınmaz**, demo tenant'la çekilir (aksi hâlde 1 numaralı kırmızı çizgi internete konmuş olur); (3) landing uygulamanın veritabanına/API'sine bağlanmaz, bağ yalnız linktir.

⛔ **Kendi kendine kayıt YOK** *(Sets, 08.08.2026)*. Müşteri portala davetle girer (`CustomerPortalInvitation` zaten var); landing'de kayıt formu bulunmaz, "iletişime geçin" der. Gerekçe sızma engelleme değil — **tenant açmak ticari bir işlemdir** (sözleşme, fiyat listesi, faturalama, hangi eğitimlerin açık olduğu); kendi kendine kaydolan firma bunların hiçbiri olmadan bir tenant satırı yaratır ve ofis onu temizler. Şimdilik form da yok, rol adresi (`bilgi@`) yeterli; form konulursa KVKK aydınlatma metni + açık rıza zorunlu.

**Tasarımcı brief'i:** `docs/design/brief-egitim-landing.md` — ürün brief'i olduğu için **eğitim repo'sunda durur**, landing repo'sunda değil.

⛔ **Üç yüzeyin farklı görünmesi bilinçlidir** *(Sets, 08.08.2026)*: `3egrp.com` çatı şirketin kurumsal sayfası, `akademi.3egrp.com` grup şirketinin firma sayfası, `egitim.3egrp.com` + paneller **ürün**. Gelen tasarımlar üç ayrı sistemde (`industry` mavi-gri/Barlow · `modernist` `#EC3013`/Archivo/köşesiz · panelin *Kırma + Mürekkep*'i) ve **birbirine yaklaştırılmayacak**. Ürün landing'i panel token setini kullanır; bağ marka işaretleriyle kurulur (ortak logo ailesi, footer çapraz bağlantıları, ton).

⚠ **Dikiş:** panel ekranı değiştiğinde landing'deki tanıtım/SSS eskir. Görünür bir panel değişikliğinde "landing'de şu sayfa güncellenmeli" notu düşülür ve diğer oturuma taşınır.

### Tablo borcu kapandı — ve son üçü "taşıma" değildi *(07.08.2026, EGT-83)*

Denetim §13.3'ün otuz iki sayfayla açtığı borç **sıfırlandı**: arch testindeki `$awaiting` listesi boş. Ölçüm turun başında yapıldı ve hafızadaki *"o taraf bitti"* yanlıştı — liste **gerekçesiz on dört sayfa** taşıyordu.

Çıkan tablo üç sınıftı, ikisi zaten biliniyordu, **üçüncüsü yeniydi**:

1. **Kayıt içi tablo** → `DetailTable`. On bir sayfa taşındı. Hepsi telefonda **kırpılıyordu** ve her biri tam da okunduğu kolondan: satışçıda koltuk sayısı, yoklamada sonuç kolonu, tedarikçide **düzeltme düğmesi** — kuralı *"silinmez, düzeltilir"* olan bir kayıtta.
2. **Matris** → kalıcı istisna, **gerekçesi hem sayfada hem listede**. Beş tane: program eğitmen kapsama ızgarası, eğitmenin yoklama defteri, müşterinin aynı defteri, planlama gündeminin talep ızgarası ve müsaitlik şeridi. Ortak yanları: **kolon kümesi veriden gelir** ve satır **yatay okunur** — kart karşılığı olan bir "satır"ları yoktur.
3. ⛔ **Kompozisyon farkı** — yeni sınıf. Son üç sayfa markup meselesi değildi; **tasarım başka bir şey çiziyordu ve kod başka bir şey yapıyordu**:
   - **Teklif ekranı** (`31:54-70`): tasarım **tek tablo, tek başlık, grup satırları tabloyu kaplıyor**; kod **grup başına başlıksız tablo** çiziyordu. Altı kolon adsızdı — okuyan üçüncü sayının birim fiyat olduğunu kendi çıkarıyordu, ve **fiyat tipi** tasarımda kendi kolonuyken bizde kalemin altına gömülüydü.
   - **Eğitim ekranı** (`22:99`): tasarım öneri isimlerini **aynı tabloda, tonlanmış** gösteriyor; kod ikinci bir ızgara açmıştı. *"Bunlar farklı bir şey"* iki kez söyleniyordu ve **"bu eğitimde kim var"** sorusu iki ızgara karşılaştırmayı gerektiriyordu. ⚠ Kuralın kendisi değişmedi: öneri kontenjana, asgari katılımcıya ve yoklama listesine **girmez** — bunu rozet, ton ve tablonun altındaki cümle taşıyor.
   - **Portal sürüm farkı** (`37:151`): tasarım başlığı **karşılaştırılan iki sürümün numarasıyla** atıyor (`v1` · `v2` · FARK); bizde başlık hiç yoktu — üstü çizili ve kalın iki para kolonu, hangisinin hangi sürüm olduğunu söylemeden.

⛔ **Ders: "bileşene taşındı" bir kompozisyon denetimi değildir.** Üç sayfanın üçünde de kolonlar doğruydu; yanlış olan **neyin nerede durduğuydu**, ve bunu ancak tasarım dosyasını açmak söyledi. Kapsama matrisinin `<table>` sayarak üretilen hâli bu üçünü **temiz gösterirdi**.

`DetailTable` bunun için üç yetenek kazandı: **satır tonu** (ters kayıt soluk, öneri tonlanmış — altı hücre slot'una dağıtılacak bir şey değil), **grup satırı + grup altlığı** (tasarım `31`'in tek tablosu ve *"bu eğitime kalem"* düğmesi ızgaranın içinde), ve **pozisyona düşen satır anahtarı** (bazı satırlar kayıt değil hesaplanmış kalem; hepsi `undefined`'a anahtarlanınca Vue yanlış düğümü geri kullanıyor).

⚠ Liste artık **iki yönlü**: matris olmayı bırakan sayfa istisna listesinden **düşmek zorunda** — `<dl>` kuralı baştan böyle yazılmıştı, tablo kuralı bugün ona hizalandı.

### Tarayıcı turu dört şey buldu — ve hiçbiri o günün kodundan değildi *(07.08.2026)*

Taşınan ekranlar `tests/Browser/detail-tables.spec.js` ile açıldı (1024 · 1440 başlık, 375 kart + yatay kaydırma yok). **10/10 geçti.** Yolda çıkanlar:

⛔ **1 · `DemoPricingSeeder` 06.08'den beri kırıktı ve kimse görmemişti.** Fiyat listesi sürümlemesi `version` kolonunu ve `UNIQUE (kind, version)` kısıtını getirdi; seeder güncellenmedi ve ikinci `daily` listesini varsayılan `version=1` ile yazmaya çalışıp patlıyordu. **Kırık olan yol "temiz kurulum + demo veri" yoluydu** — yani yeni bir makinede projeyi ayağa kaldırmak. Ders tanıdık: *bir kolon eklerken onu yazan **her** yol aranır*, üretim yolu (`OpenNextPriceListVersion`, kilitli, en yüksek + 1) baştan doğruydu, seeder unutulmuştu.

⛔ **2 · Reverb ayakta değildi** — `launchd` işi yüklüydü ama koşmuyordu, ve sonucu ofis panelinin **her ekranında** konsola düşen WebSocket 502'ydi. ⚠ Bunu yakalayan şey testin *"konsol temiz olacak"* şartıydı; ekrana bakan biri hiçbir şey fark etmezdi.

⚠ **3 · Program detayına geri dönüş yok.** `/paketler/{package}` yalnız planlama sonrası **yönlendirmeyle** açılıyor; programa bağlı bir eğitimin ekranında programa giden bağlantı yok ve payload programı taşımıyor. Ekran var, yol tek yönlü. *(Onay kuralı 4'ün "wire varlığı" sorusunun tam karşılığı: caller VAR ama bir tane ve tek yönlü.)*

⚠ **4 · Liste satırları bağlantı değil düğme.** Teklif, tedarikçi ve eğitmen listelerinde satır `<button @click="router.get(...)">` açıyor — orta tıklanamaz, kopyalanamaz, ekran okuyucuya hedef olarak okunmaz. 05.08'de *"satırın adı kaydı açar (bağlantı)"* diye yazılan kural bu üç listede tutulmamış.

### Discord bir hata beslemesi değil — süreç takibi *(Sets, 07.08.2026, EGT-37)*

Sentry hataları ve kendi kanallarını tutar. Discord, **izlenen bir sistemin akan yorumudur**: kim girdi, hangi teklif açıldı, hangi fatura kesildi. Üç kanal, **kime ait olduğuna** göre bölünmüş — şiddete göre değil:

| Kanal | Ne düşer |
|---|---|
| **office** | Ofisin kendi dünyası: girişleri, yeni teklif, yeni eğitim, yeni planlama |
| **customer** | Müşteri girişleri ve müşteri aksiyonları (teklif onayı) |
| **accounting** | Fatura, tahsilat, masraf, eğitmen ödemesi |

⛔ **Girişler ANLIK, günlük özet değil.** Plan (chunk7 §7.9 kriter 15) *"normal girişler anlık Discord mesajı üretmiyor, günlük özet üretiyor"* diyordu; Sets bunu geçersiz kıldı: *besleme sistemin izlendiği yer, kullanımın görünmesi gerekiyor.* Özet *"dün kaç kişi"* sorusunu cevaplar; izleme *"şu an kullanan var mı"* sorusunu — ve beslemenin var olma sebebi ikincisi.

⛔ **`debug` ve `prod-debug` kanalları uygulamanın değil.** Sentry'nin bot'una ait; oraya yazan bir uygulama satırı, o kanalı açık tutan kişinin beklediği uyarıyı gömer. `config/notifications.php` `.env`'deki beş webhook'un yalnız üçünü okur ve **arch testi bunu kilitler**.

⛔ **Bağlanma noktası `AuditWriter::record`** — her iş olayı zaten oradan geçiyor. Alternatif her action'a bir satırdı: bugün altı düzenleme, gelecek ay unutulan yedincisi. ⚠ Besleme **seçilmiş bir kısa listedir**, denetim izinin sohbet kanalına boşaltılması değil: her satırı taşıyan kanal kimsenin okumadığı kanaldır.

⚠ **Açık bırakılan gerilim:** müşteri girişlerinin buraya düşmesi **AD-21/L-1a ile sürtüşüyor** — o istisna müşteri kullanıcısı için *yalnız son aktif zaman damgasına* izin verir, giriş geçmişine değil. Ofis tarafı L-1b ile zaten serbest. Sets'e soruldu ve uygulama bu hâliyle bırakıldı; daraltmak gerekirse tek yer `RecordAuthenticationEvent::tellDiscord`.


### Hata izleme — yalnız production *(Sets, 10.08.2026, EGT-95)*

Faz 0'da (EGT-15) Sentry projeleri, `3eegitim-outage-bot` ve Discord kanalları kurulmuştu; **uygulama tarafı bağlı değildi.** Ölçüldü: `Integration::handles($exceptions)` hiçbir yerde çağrılmıyordu ve Sentry paketi PHP SDK'nın kendi hata dinleyicilerini bilerek kaldırdığı için **tek bir exception bile gönderilmemişti** — paket kurulu, DSN dolu, `AppServiceProvider` gönderilmeyen bir trace'ten argümanları saklıyor. Sessizce kapalı bir izleme, boş bir issue listesini *"hiçbir şey bozuk değil"* diye okutur.

| Karar | Nerede |
|---|---|
| **Yalnız production izlenir** — bu makine de local prod (1.210) da göndermez. Kapı örnekleme oranı değil **DSN**: oran sıfırken bile olay kurulur, `before_send` koşar, sonra atılır | `config/sentry.php`, `config/sentry_javascript.php` |
| Ayrım `APP_ENV` ile **değil** `SENTRY_ENVIRONMENT` ile — 1.210'un `.env`'i de `production` der | aynı |
| **Tracing ve profiling kapalı** — soru "bir şey bozuldu mu", "kaç ms sürdü" değil | `sentry.traces_sample_rate` |
| Exception'lar tek yoldan gider (`SentryReporting::handles`); `sentry` log kanalı `report_exceptions: false` ile **yalnız `Log::critical`** taşır (audit zinciri doğrulanmadığında olduğu gibi) — ikisi açık olsa aynı çökme iki kez ticket açardı | `bootstrap/app.php`, `config/logging.php` |
| **PII gönderilmez**: kişi yalnız id; istek gövdesi ve query string **audit trail'in `Masker`'ı ile** maskelenir; çerez ve başlıklar tamamen düşer | `app/Integrations/Sentry/PrivacyFilter.php` |
| Frontend: office ve portal kendi bundle'ından, backoffice **render hook + kendi Vite entry'si** ile (Filament varlık listesi boot'ta okunuyor, oraya konamaz). **Session Replay YOK** — ekranlarda TC kimlik, telefon, sertifika verisi var | `resources/js/lib/sentry.js`, `resources/js/backoffice.js` |
| Kaynak haritaları `hidden` üretilir, deploy'da Sentry'ye yüklenir ve **diskten silinir** | `vite.config.js`, `deployment/scripts/deploy.sh` |
| Sentry alert kuralları → **#prod-debug** (yeni hata + regresyon, dakikada 10'dan fazla hata). Varsayılan e-posta kuralı kaldırıldı | Sentry UI (API ile kuruldu) |
| Bot artık yalnız `production` poll ediyor (önce `local,local-prod,production` idi — OPS-1 tam da böyle açılmıştı) | `services/outage-bot/wrangler.jsonc` |

⚠ **Yeni her issue bir OPS ticket'ıdır.** Bu yüzden gürültü doğrudan tracker'a yazılır: `ignore_exceptions` listesi (doğrulama hatası, 404, süresi dolmuş form, hız sınırı) ve frontend `ignoreErrors` listesi süs değil, o kapının filtresidir.

⛔ **Aynı hata pencere başına tek olay** *(Sets, 12.08.2026, OPS-5)*. `before_send` artık `BeforeSend` üzerinden geçiyor: önce tekrar kısıtlayıcı (`SENTRY_REPEAT_WINDOW`, varsayılan 60 sn), sonra maskeleme. Sebep ölçüldü: portu taşınmış Redis'e yeniden bağlanmaya çalışan bir daemon **bir günde 92.683 olay** yazdı, hepsi aynı üç satır. ⚠ Pencere **sürecin kendi belleğinde** tutulur — önbelleğe soran bir kısıtlayıcı, tam da önbelleğe ulaşılamadığı anda çalışmaz ve o hatayı da raporlardı. ⚠ Ticket sayısını değiştirmez (o olaylar zaten tek issue idi), gönderilen olay sayısını değiştirir. Ayarın yeri `config/monitoring.php`: `config/sentry.php` SDK'nın kendi seçenek çözücüsüne gider ve tanımadığı anahtarı **reddeder**.

⛔ **Servisler bağımlılıklarını gerçekten bekler** *(12.08.2026, OPS-5)*. Her şey docker'da koşuyor ve `After=docker.service` yalnız daemon'un başladığını söyler; container'lar sonra, asenkron kalkar. Horizon'un beklediği `redis.service` ise sistemde hiç yoktu — var olmayan bir birim `After=` içinde **sessizce yok sayılır**. Artık iki birim de `deployment/scripts/wait-for-services.sh`'i `ExecStartPre` olarak koşuyor (portlar `.env`'den, Redis'e gerçek `PING`, süreyle sınırlı). ⚠ `StartLimitIntervalSec=0` şart: hazırlık kontrolü hızlı hızlı düşerse systemd 10 sn'de 5 denemeden sonra **kalıcı olarak vazgeçer** — korunmak istenen hâlin systemd eliyle olanı. Canlıda ölçüldü: olmayan bir port bekletildiğinde `ExecStart` hiç koşmuyor, `Restart=always` ile de vazgeçmeden bekliyor.

⛔ **Tayfun OPS'ta da Assignee olabilir** *(12.08.2026)*. EGT'de 10.08'de yapılan işin aynısı OPS için yapılmamıştı; `In Review` durumu paylaşılan bundle'da olduğu için zaten vardı ama atama reddediliyordu. Tayfun OPS'un Assignee bundle'ına (`13-4`) bireysel olarak eklendi — grup ekleme Cloud API'sinden hâlâ yapılamıyor.

#### İlk üç OPS ticket'ı ne öğretti *(12.08.2026)*

Üçünün ikisi geçiş artığıydı, biri gerçek hataydı — ve **gerçek olanı ekran görüntüsünde hiç görünmüyordu**.

- **OPS-2 / OPS-3 (Redis bağlantı reddi).** 11.08 gecesi Redis blok-2 portlarına taşınırken uygulama bir dakika kadar eski cache'lenmiş yapılandırmayla koştu. Kod hatası değil; Horizon yeniden başlayınca kesildi. ⚠ Yan bulgu **OPS-5** oldu: `3eegitim-horizon.service` birimi `After=redis.service` diyor, o birim sunucuda **yok** — Redis docker'da koşuyor. Yani "önce Redis" sırası fiilen çalışmıyor ve aynı hata bir reboot'ta tekrar edebilir.
- **OPS-4 (rakam alanlarında tuş adı okuması).** `event.key.length` üç kontrolde birden yazılıydı; sentetik bir keydown'da (parola yöneticisi, eklenti) `key` tanımsız geliyor ve ekran ilk tuşta ölüyordu. ⛔ Ders: **bir atomun kopyalanan satırı, kopyalandığı her ekranın hatasıdır.** Regresyon kontrolü `FrontendTest`'e yazıldı.
- ⛔ **`.env` şablonu testleri toolkit geçişinde kırıldı ve iki gün sonra fark edildi** — arada CI koşmadığı için. `YOUTRACK_*`/`TEAMCITY_*` anahtarlarını okuyan betikler `.toolkit/` submodule'üne taşındı; submodule CI checkout'unda **boş** olduğu için taranamaz, anahtarlar `$packageOwned` ile aynı gerekçeyle muaf listesine alındı. Aynı turda `REDIS_*_PORT` anahtarları da gerçekten okunur hâle getirildi: compose'lardaki sabit portlar bu değişkenlere bağlandı (varsayılanlar birebir aynı).

### Firma türü — ve bir ticket'ın nasıl bölündüğü *(12.08.2026, EGT-110)*

⛔ **Sistem kullanıcı adına varsayım yapmaz — öneri eder** *(Sets, 12.08.2026 vetosu + 13.08.2026 kapsamı)*. Tayfun'un *"firma türü ve yetki alanı kapsamındaki tüm eğitimler işaretli gelsin"* önerisi **reddedildi**: kullanıcılar okumadan "ileri ileri" geçer, hepsi işaretli gelirse okunmadan onaylanır — ve o andan sonra **bizim planlamacımız ve satışçımız da yanılır**, *"şurada potansiyel var"* der, oysa orada okumamış bir yönetici vardır. Yerine konan şey:

| Nerede | Ne olur |
|---|---|
| **Backoffice** | Hangi yetki belgesi hangi eğitimleri kapsıyor — **yönetici tanımlı eşleştirme ekranı**. Kodda sabit eğitim listesi yok (kırmızı çizgi 14). |
| **Firma paneli → personel detayı** | *"Yetki belgeleriniz kapsamında personelinizin alabileceği eğitimler"* kartı: listeler, yönetici oradan **seçip teklif isteğine ekler**. |
| **Planlamacı listesi** | Kapsam dışı eğitim **listede kalır**, yanında uyarı çıkar (EGT-112). |

⛔ **Yapılmayacaklar, açıkça:** önceden işaretli gelen eğitim · sistemin kendiliğinden kayıt açması · kişi kişi *"bu personelin şu eğitimi eksik"* · her ekranda tekrarlayan uyarı. Sets'in ölçütü: *"sadece bir bilgi ekranı olarak bunları söyleyebiliriz ama bu taciz eden bir hâle dönüşmemeli."* Kişi bazlı "beklenen eğitim" kaydı **yapılmıyor**; uyum hesabı bugünkü hâliyle (sertifikası var mı / doluyor mu) kalıyor. EGT-115 bu yüzden kapatıldı: Görev 10 bir seçenek olarak listede durur, satışçı firmanın belgesini görünce kendisi fark eder.

Tayfun **on işi tek ticket'a** yazdı (firma türü, firmanın yetki belgeleri, planlamacının listesinin daralması, saha/ofis ayrımı, 3 ay kuralının kapanması, kişiye eğitim işaretleme, Görev 10'un otomatik gelmesi). Ticket bölündü, kural kendisine yazıldı: **her iş ayrı ticket** — birleşik ticket kapanamaz, test edilemez, sıralanamaz. Kalan beşi ayrı açıldı ve **Tayfun'a atandı**: EGT-111 (firma yetki belgeleri ekranı — tablo var, yazan yok), EGT-112 (kapsam daraltma ⛔ karar bekliyor), EGT-113 (saha/ofis), EGT-114 (kişiye eğitim işaretleme), EGT-115 (Görev 10).

✅ **EGT-112 kararı verildi (Sets, 13.08.2026): uyarı yazılır, gizlenmez.** Planlamacının listesinde tüm eğitimler kalır; firmanın yetki belgesinin kapsamadıklarının yanına *"firmanın yetki belgesi bunu kapsamıyor"* yazılır ve planlamacı yine seçebilir. Gizleme reddedildi çünkü belgesi henüz yüklenmemiş bir firmada planlamacı eğitimi arar, bulamaz ve **sebebini de göremez** — satışın önü sebebi görünmeden kapanır.

✅ **EGT-217 kararı (Sets, 20.10.2026): müşteri kataloğunda da gizlenmez, kapsamdakiler öne alınır.** Tayfun «kapsam dışı eğitimler müşteriye listelenmesin» istedi; teklif isteği kataloğunda bütün eğitimler kalır, firmanın bugün geçerli yetki belgeleri kapsamındakiler **üstte ve «Belgeniz kapsamında» işaretiyle**, firmanın sahip olmadığı belgeyi isteyenlerin yanında **hangi belgeyi istediği** yazar. Gizleme yine reddedildi (EGT-112 ile aynı gerekçe: belgesini yüklememiş firma eğitimi bulamaz ve sebebini göremez).

⛔ **EGT-218 kararları — mevzuat dışı eğitimler ve marka** *(Sets, 20.10.2026)*: (1) Eğitim tanımı bir **kategori** taşır: *mevzuat eğitimi* ya da *mevzuat dışı eğitim* — *"seçenek değil kategori olarak yapalım"*. Mevzuat eğitiminde **mevzuat ve bağlam zorunlu**; mevzuat dışında bu zorunluluk yok, **evrak yine bağlanabilir**. (2) İçerik sahibi bir **marka** olarak kurgulanır (WeNexus, yarın BeNexus…): *"wenexus içerikleri hazırlıyor, onun eğitimi veriliyor… brand gibi kurgulayalım"* — backoffice'te tanımlı liste, tanımda isteğe bağlı; **katalog + teklif + belgede** görünür. (3) **Belge düzenleyebilme/verebilme her iki kategoride de isteğe bağlı bir seçenektir**, eğitim tanımında seçilir; belgeyi kimin düzenlediği de tanımda seçilir — *"mevzuat eğitimlerinde belgeyi mevzuatın sahibi veriyor… bazen eğitim veren kuruluşa belgeyi düzenleme yetkisi de verebiliyorlar (MEB'in bazı sertifikasyon eğitimleri gibi)… eğitim tanımında seçilsin"*. (4) Süreç: kısa plan + heyet, sonra kod ve uygulama incelemesi. (5) **Belge üretimi iki yollu** *(Sets, 20.10.2026)*: belgeyi düzenleyen 3E ya da marka ise, **belge şablonu yüklenirse sistem her katılımcının belgesini üretir** (şablon = boş belge: arka plan, logo, imzalar; sistem ad, eğitim, tarih, numarayı basar; uygun ölçüde şablon şartı — antet yüklemesi gibi), **«dışarıda hazırlayacağız» seçilirse sistem yalnız belge numarası, tarih, geçerlilik gibi gereklilikleri tutar** (bugünkü kayıt). Yazdırılabilir belgenin yerleşimi tasarımcıdan istenir; bu kısım **ayrı ticket**: EGT-225, brief `docs/design/katilim-belgesi-brief.md`. **EGT-225 belge içeriği** *(Sets, 20.10.2026)*: T.C. kimlik no **açık**; belge numarasını **sistem verir, düzenleyen başına sıralı seri**; ~~**QR + herkese açık doğrulama sayfası** (kimlik numarası göstermez)~~ *(yerine geçti: aşağıdaki «EGT-225 tasarım 52 kararları» madde 2)*. ⛔ **EGT-225 tasarım `52` kararları** *(Sets, 20.10.2026, tasarım teslim edildikten sonra)*: (1) **Ayrı «belge seri öneki» alanı** markalara ve 3E şirketlerine eklenir; fatura serisinden bağımsız (ör. WNX, 3EA). (2) **Doğrulama sayfası mevcut panel adresinde bir yoldur ve giriş zorunludur — yalnız panele giriş yapmış kullanıcılar doğrular** (*"bence mevcut panel adresinden yapalım ve authentication zorunlu olsun"*); tasarımdaki `dogrula.3egrp.com` alt alanı ve «herkese açık, giriş yok» notu **uygulanmaz** — hesabı olmayan denetçi ya da başka firma QR'dan doğrulayamaz, bu bilinçli. (3) **Şablon PNG ya da tek sayfalık PDF**; PDF'i görsele çevirmek için **canlı sunucuya `poppler-utils` kurulur** (bugün yalnız PHP GD var). (4) **Yeni ofis izni: belgeyi düzeltme/iptal**; gerekçe zorunlu, denetim kaydına yazılır; düzeltmede yeni numara, eski belge «geçersiz». (5) Süreç: plan `docs/plans/egitim-belgesi-plan.md` + heyet, sonra kod ve uygulama incelemesi *(Sets, 20.10.2026)*. (6) **Mevzuat dışı = mevzuatsız** *(Sets, 20.10.2026, heyet 828 F-1 sorusu)*: mevzuat dışı eğitime mevzuat ya da ek mevzuat bağlanamaz; bakanlık akışı ve mevzuat uyarıları hiç çalışmaz; evrak (firma belgesi beklentisi) bağlanabilir.

- **Durum — EGT-218 (20.10.2026):** plan `docs/plans/mevzuat-disi-egitim-plan.md` v1.2 (heyet 828 ✗ 7 → 829 ✗ 1 → 830 ✓). Kod `b4654932`: migration'lar `2026_11_04_000100` (kategori, marka, belge modu/düzenleyen, iki yönlü CHECK'ler), `_000200` (mevzuat dışında bakanlık alanları boş — M-11'i veride sabitleyen ek CHECK), `_000300` (`brand_id` indeksi); `Brand` + `BrandPolicy` + `brand.*` izinleri + backoffice «Markalar»; tanım formunda kategori (eğitimi varsa kilitli), marka, «Katılımcı belgesi»; mevzuat dışı eğitim `Planned`'dan başlar, KUMS'suz biter, bakanlık eylemleri politikada reddedilir; plan ekranında «Mevzuat dışı eğitimler» kartı; marka katalog + teklif metni/çıktısında, faturada değil; `CertificateIssuer` belge kaydına düzenleyen adını kopyalar. Testler: `NonRegulatoryTrainingTest` (16), `BrandResourceTest` (3), `BrandOnQuoteTest` (1). Uygulama incelemesi 831 ✗ (1: bu durum satırı) → 832 ✓. TeamCity #1833 yeşil (2765), prod `20261020-224216` (canlıda 32 tanım `regulatory` + `issued_elsewhere`, `brand.*` izinleri var). EGT-218 → In Review (Tayfun). EGT-44 aynı yayınla kanıtlarla Fixed (içerik güçlü oturum ister; `ContentPackageTest` kilidi). Şablondan belge üretimi EGT-225'te.
- **Durum — EGT-225 (21.10.2026):** tasarım `52` ✓, plan `docs/plans/egitim-belgesi-plan.md` v1.2 (heyet 834 ✗ 6 → 837 ✗ 2 → 839 ✓). Kod: migration `2026_11_05_000100` (generated modu + 3E/marka CHECK, iki tabloda belge seri öneki, `certificate_number_sequences`, tanım başına şablon sürümü, kayıtta durum/düzeltme/şifreli kopya, kişi başı tek **geçerli** belge, kiracısız `certificate_verifications`) + `_000200` (doğrulama satırında sahibin firması — erişim kaydı içindir); `GenerateCertificate` (tamamlama, sonradan geçen sınav, şablon yüklemesi) → `CertificatePrinter`; `CorrectCertificate` / `CancelCertificate` (`certificate.void`); `UploadCertificateTemplate` (+ `CertificateTemplateFile`, `PdfRasterizer`); backoffice «Belge» bölümü, marka/şirket öneki; ofis «Katılımcılar ve belgeler» kartı + şerit + belge + `/dogrula`; portal kartı + bekleyenler, katılımcının kendi belgesi, panelde `/dogrula`. Kapsama: `docs/reviews/tasarim-52-kapsama.md` (4 ⚠ soruda). ⛔ **Plandan ayrılan ya da plana eklenen uygulama kararları — Sets 21.10.2026'da (a), (b), (d), (e), (g)'yi onayladı (heyet 841 F-1); tasarım 52 kapsama ⚠ satırları 10, 12, 32, 38 «böyle kalsın» (Filament'in yükleme alanı ve alan hatası; genel «tamamlamadı» nedeni; portalda tasarım 16 kartı):** (a) kopya `encrypted:array` değil **firmanın anahtarıyla** şifreli (`TenantEncryptedArray`, AD-05); (b) asgari çözünürlük sabit değil **iş ayarı** (`company.certificate_template_min_dpi`, varsayılan 300 — kırmızı çizgi 9); (c) doğrulama iletişim adresi yeni iş ayarı `company.certificate_contact_email`; (d) bulunamayan numaranın sorgusu sahibi olmadığı için **okuyanın** firmasına (ofiste 3E'nin kaydına) yazılır; (e) düzeltilen/iptal edilen belge modelin varsayılan kapsamıyla bütün uyum/yenileme/portal okumalarından düşer (`CertificateRecord::VALID_ONLY`), ham sorgulu dört okuyucuya aynı süzgeç eklendi; (f) portalda belgeyi sertifikalar ekranının yetkisi (`customer.manage_participants`) açar; (g) iptal edilen belge sonraki tetikleyicide yeniden basılmaz. Testler: `PrintedCertificateTest` (planlama 16, müşteri 5), `CertificateTemplateTest` (8), fixture ölçümü `training-certificate-sheet.spec.js` (12), tarayıcı `training-certificate.spec.js` 4/4 (375 + 1440: ofis eğitim sayfası → belge, doğrulama; dev'e Sets onayıyla örnek veri eklendi — tanım 33 «IMDG Farkındalık Eğitimi (örnek belge)», eğitim 11, CEVA, belge `WNX2026000000001`, silinen bir şey yok), eşzamanlılık `OmurgaRls/CertificateNumberRaceTest` (2). Uygulama incelemesi 841 ✗ 6 (F-1/F-2 Sets kararıyla kapandı, F-3/F-4 testleri yazıldı, F-5 poppler kuruldu, F-6 tarayıcı koşuldu). Canlı: `poppler-utils 22.12.0` kurulu (`docs/infra/ortamlar.md`).

⛔ **Karar öncesi durum (kayıt için):** "Planlamacı kapsam dışını listelemesin" ürünün üç yerde verdiği kararın tersi: zorunlu evrak bloklaması EGT-97'de kaldırıldı, firmadan beklenen belge EGT-79'da uyarı olarak yazıldı. Seçenekler ticket'ta: uyarı · varsayılan süz-açılabilir · gizle.

✅ **Uygulanan madde: firma türü.** `customers.kind_option_value_id` + yeni `FIRMA-TURU` seçenek listesi (Gözetim → deniz ticareti / tehlikeli madde · Lojistik · Sanayi · Liman · Diğer). ⛔ `legal_type` bu soruyu **hiç cevaplamıyordu**: o faturayı kimin imzaladığını söyler. Zincirin geri kalanı (hangi belge beklenir, hangi eğitim zorunlu, planlamacı neyi önerir) bu alandan okuyacak.

- ⚠ **İki seviye, TEK kolon.** Seçilen değer saklanır, üstü `parent_id`'den okunur; çift kolon birbiriyle çelişebilirdi. Etiketi `OptionListValue::qualifiedName()` yazar — ikinci bir yazım ikinci bir cevap olurdu.
- ⚠ **Alan nullable.** Prod'daki 13 firma türsüz; eski kayda tür uydurmak yerine boş bırakıldı, ekran «—» çiziyor.
- ⛔ **`ActiveValueOfOptionList` kuralı yazıldı.** Ev deseni `Rule::exists('option_list_values', 'id')` idi — yani bir **il** de firma türü olarak kaydedilebilirdi; hiçbir şey hata vermez, ekranda hiçbir şey yanlış görünmezdi. Kural değeri listeye ve aktifliğe bağlar; sonraki maddeler (EGT-113/114) de bunu kullanacak.
- ⛔ **Liste migration'da da tohumlanır.** Deploy yalnız `AuthorizationSeeder` koşuyor: kolonu taşıyan ilk sürüm canlıya **boş bir seçimle** çıktı. `customer_locations` aynı dersi 29.08'de almıştı; şekil birebir kopyalandı.

### ✅ Audit zinciri ekranı yazıldı *(10.08.2026, EGT-92)*

AD-06'nın "delil" iddiası, zincirin **uygulama kodumuz olmadan** yeniden hesaplanabilmesine dayanır ve bu 26.07'de fiilen kanıtlandı. Eksik olan, bilirkişiye **teslim edilebilir** bir araçtı. Kararlar `docs/specs/audit-zinciri-ekrani-spec.md`'de, plan `docs/plans/audit-zinciri-ekrani-plan.md`'de; harita yalnız yönü tutar:

- **İki katman, tek ekran:** vitrin (`audit.view` — zincirin durumu, mühür anatomisi, log yapısı) ve araç (yeni **`audit.verify`** — doğrulama koşturur, delil paketi indirir).
- ⛔ **Sunucuda bizim kodumuzla koşan doğrulama iddiayı kanıtlamaz, tekrarlar.** Ekrandaki canlı doğrulama günlük kullanım; delil değeri **pakette** — çünkü paket bizden bağımsız koşar (`dogrula.py`, yalnız psycopg + hashlib).
- ⛔ **Pakette audit satırı YOK** — satırlar kişisel veri, kök kapsadığı şeyi ele vermez. Bilirkişi satırları veritabanından kendi okur.
- **Bulgu kaydı kapatılamaz** (AD-11): kırılmış zincir bulgusunu "çözüldü" yapmak, delili yönetilebilir yapmaktır. Not düşülür, o kadar.
- ⛔ **Uygulanırken bulunan iki şey.** (1) Filament paneli **kendi bundle'ını** servis eder ve içinde yalnız Filament'in kullandığı utility sınıfları vardır — sayfada yazılan `grid`, `rounded-xl`, `text-2xl` **hiçe derlenir**; ekran 200 döner, dört kart alt alta yığılır, tablo düz metin olur ve hiçbir yerde hata çıkmaz (tarayıcıda ölçüldü). Sayfa kendi `<style>` bloğunu ve `ac-` önekli sınıflarını taşır. (2) SQL fonksiyonunda **`from_id` dışlayıcıdır** (`id > from_id`); kapsayıcı okunsaydı her mühre fazladan bir satır katılır ve hiçbir kök tutmazdı.
- ⛔ **"Sağlam" = doğrulanmış ve temiz.** İlk sürüm, hiç doğrulanmamış bir zincire mühür varsa "✓ Sağlam" diyor, altına "Hiç doğrulanmadı" yazıyordu. `VerifyAuditChain` artık **temiz koşuyu da** kaydeder (`audit_chain.verified`) — yoksa "bir saat önce bakıldı" ile "hiç bakılmadı" ayırt edilemez.
- **`audit_usage` neden mühürlenmiyor** sorusu ekranda gerekçesiyle cevaplanır — *"sisteme ne zaman girmiş"* zaten `audit_writes`'ta ve o tablo mühürlü; usage'ın yazma güvencesi gevşek, mühürlemek zincire tutamayacağı bir "tam kayıt" iddiası yükler. ⚠ Mühürlenmemek silinmek değildir (07.08 kararı).

### Dilim ≠ koşul çipi — `DataTable`'ın kazandığı ayrım *(07.08.2026, EGT-71)*

Üç liste tasarımı (`61:39-44` eğitmen, `25:40-47` tedarikçi, `63:35-39` kullanıcı) tabloyu **sayaçlı bir çip satırıyla** açıyor: `Tümü 14 · Belgeler tam 11 · Belge uyarısı 2 · Engelli 1`. Üçünde de bu satır, kataloğun `02:37`'de standartlaştırdığı **koşul çipiyle aynı yerde ama aynı şey değil**:

| | Koşul çipi (`02:37`) | Dilim (`61`, `25`, `63`) |
|---|---|---|
| Cevapladığı soru | *"şu an neye göre süzüyorum"* | *"bu liste nasıl bölünüyor, hangisinde kaç tane"* |
| Ne zaman görünür | eklediğinde | **sen dokunmadan** |
| Kaç tane aktif | üst üste biner (`Kocaeli AND (dolmuş OR dolacak)`) | **bir tane**, ilki sıfırlar |
| Sayaç taşır mı | hayır | **evet — var olma sebebi o** |

⛔ **Tasarımlar ikisini birden çiziyor, yan yana** — `25`'in başlığında hem tür sekmeleri hem sağda `İl: Hatay ✕` var. Bizde yalnız koşul çipleri vardı ve dilimler `＋ Filtre` menüsünün arkasındaydı; yani **insanın o ekranı açma sebebi olan sayılar** görünmüyordu. `DataTable` `slices` prop'unu kazandı (yatay kayar, sarmaz — tedarikçi türleri yönetilen listeden gelir ve bir tür eklendiğinde tablo bir satır aşağı inmemeli).

⚠ **Bu bulgu bir ara raporu düzeltti.** Aynı turda `63` ve `25` "✓" verilmişti; sayım kolonlara bakıp başlığa bakmamıştı. Ders: *kapsama matrisi tabloyu değil **ekranı** sayar.*

⛔ **Açılan her katman kendi kutusundan ÇIKAR** *(12.08.2026 EGT-106, 13.08.2026 EGT-119)*. Satır menüsü (`RowMenu`) tablonun `overflow-x-auto` kutusunun içinde `absolute` konumlanıyordu; **tek eksende kayan bir kutu diğerini de kırpar**, dolayısıyla menünün alt kalemleri canlıda hiç görünmüyordu — ve hiçbir yerde hata çıkmıyordu, ilk kalem görünüyordu. Panel artık `body`'ye teleport ediliyor, işaretin kendi dikdörtgeninden konumlanıyor, yer yoksa yukarı açılıyor. ⚠ Kural bileşene değil **yere** aittir: bir tablonun satırından açılan her katman (menü, popover, tarih seçici) aynı sebeple aynı şeyi yapmak zorundadır. Toolbar'daki `＋ Filtre` penceresi kapsam dışıdır — o kaydırma kutusunun dışında duruyor.

⚠ **Kural yazılmıştı ve yalnız menüye uygulanmıştı; bir gün sonra aynı arıza tarih seçicide çıktı** *(EGT-119, Tayfun: teklif isteğinde takvim kartın kenarında kesiliyor — bir ay açılıyor, iki hafta görünüyor)*. Bu yüzden aritmetik artık **ortak**: `lib/anchored-layer.js` (`useAnchoredLayer`). Katman `<body>`'ye taşınır, alanın dikdörtgeninden konumlanır, altta yer yoksa yukarı açılır, sığmazsa kendi içinde kayar, pencerenin kenarını geçmez, kaydırma ve yeniden boyutlandırmada yeniden hesaplanır. **Altı kontrolün hepsi ona bağlı:** `DatePicker` · `DateRangePicker` (yalnız popover kipi) · `SearchableSelect` · `MultiSelect` · `PhoneInput` · `ScopePill`. Yedinci bir açılır kontrol yazan onu da bağlar — kopyalamaz.

### ⛔ Demo veri eksikliği bir ekranı gizliyordu — altında İKİ hata vardı *(07.08.2026)*

Demo sette **çok sürümlü teklif yoktu** (dört teklifin hiçbirinde ikinci sürüm), dolayısıyla portalın **sürüm karşılaştırma** bloğu — tasarım `37`'nin sayfanın üçte birini ayırdığı şey — **hiç çizilmemişti.** `DemoQuotesSeeder`'a gerçek yoldan geçen revizyonlu bir teklif eklendi (müşteri revizyon ister → satışçı kalem ekler → ikinci sürüm gönderilir). Ekran açılınca altından iki hata çıktı:

1. ⛔ **Controller sürüm numarasını yanlış yerden okuyordu.** `MyQuotes::detail()` sürümü bir **blok** olarak döndürüyor (`['number' => …, 'body' => …, 'content_hash' => …]`); liste satırı ise düz sayı. Controller onu sayı sanıp `(int)` çeviriyordu. PHP'de **dizi her tam sayıdan büyüktür**, yani koşul hep doğru; cast hep `1` üretiyor; karşılaştırma 0 ile 1'i soruyor, `null` dönüyor. Sonuç: **blok hiç kimseye çizilmedi.**
2. ⛔ **`QuoteVersionDiff` var olmayan bir metin biçimini ayrıştırıyordu.** Sınıf gönderilen METNİ okur (bilinçli: müşteri neyi gördüyse onun üzerinden karşılaştırılır) — ama beklediği biçim `Kalem · ₺1.234`'tü: belgenin hiç kullanmadığı bir ayraç ve **sayının yanlış tarafında** para birimi. `QuoteDocument` gerçekte `1) Görev 3 — 8 × 2.400,00 ₺ = 19.200,00 ₺` yazıyor. Hiçbir satır eşleşmiyor, hiçbir şey hata vermiyor, tablo **boş** çiziliyordu.

⚠ **Testler bunu neden yakalamadı:** iki mevcut test gövdeyi **kendisi uyduruyordu**, üstelik ayrıştırıcının beklediği biçimde. Test ile kod birbirini doğruluyor, ikisi birden gerçekle çelişiyordu. Yeni test gövdeyi **`QuoteDocument`'ten alıyor** — üretici ile ayrıştırıcı artık kırmızı test olmadan ayrışamaz. Aynı ilke: *ayrıştırıcı, üreteni okuyarak yazılır.*

⛔ **Genel ders:** *demo verisi olmayan ekran, görülmemiş ekrandır.* Tasarımı olan her ekranın demo karşılığı olmalı — yoksa hata canlıda, müşterinin önünde bulunur.

### Depolama: ilk fazda YEREL şifreli disk *(Sets, 05.08.2026)*

*"S3 mü kullanacağız yoksa Cloudflare R2 mi? Hazırda Cloudflare'imiz var, ekstra S3 almaya gerek var mı? Ayrıca ilk fazda direkt olarak encrypted storage'da local'de tutsak daha iyi değil mi, en baştan ayırmaya gerek var mı?"* — ardından: *"İlk aşamada production'a çıkarken de R2 açmayacağım, ileride bakacağız gelişmelere göre."*

**Karar:** `DOCUMENT_DISK=local`, **production dahil**. `SecureFileStore` baytları yazmadan önce şifreliyor ve okurken özetini doğruluyor; disk yalnız bayt tutar, hangisi olduğu tek satırlık bir karardır. Ayrılması için mimari bir gerekçe yok.

**Neden R2, S3 değil (ileride):** R2 S3 uyumludur — aynı sürücü, aynı bileşen, yalnız endpoint ve kimlik değişir, **kod değişmez**. Asıl fark **çıkış trafiği ücretsiz**: bu ürün doküman okutur (her sertifika görüntüleme, her paket açılışı, her evrak önizlemesi bir indirmedir) ve S3'te fatura kalemi tam orasıdır. Ayrıca Cloudflare zaten var; ikinci sağlayıcı, ikinci fatura, ikinci anahtar seti.

⛔ **Yerel diskin sunucuda güvenli olması iki şarta bağlı ve biri eksikti:**
- `deploy.sh` `storage`'ı sürümler arası **paylaşıyor** — yüklenen evrak deploy'u atlatıyor. Bu zaten vardı.
- `backup.sh` **yalnız veritabanını** alıyordu. Geri yükleyince elde var olmayan dosyaları işaret eden satırlar kalırdı: imzalı yoklama, sertifika, lisanslı doküman. **Kayıt durur, belge gider.** Artık dökümün yanına belgeleri de arşivliyor, dizinin **içinden** tar'lıyor (mutlak yol taşıyan arşiv başka makinenin ağacını kurar), ikisini aynı saklama süresiyle tutup uzağa kopyalıyor, ve disk yerel değilse **boş arşiv üretmeyi reddediyor**.

⛔ **`--verify` hiç tamamlanmıyormuş.** `pg_restore` *atlanan* hatalar için de sıfırdan farklı döner ve TimescaleDB dökümünde bunlar kaçınılmazdır (hypertable `ALTER TABLE ONLY` kabul etmez); `set -e` betiği tam orada kesiyordu. *"Denenmemiş yedek, yedek değildir"* adımının kendisi denenmemişti. Karar artık çıkış koduna değil **kayıt sayısına** bakıyor — tablo değil satır sayıyor, çünkü boş bir şema da kusursuz geri yüklenir.

⛔ **`.env.production.example` gitignore'daymış** (`.env.*` deseni). Temiz bir klonda **deploy günü açılacak tek şablon yoktu** ve "iki şablon aynı anahtar kümesinde kalmalı" arch kuralının okuyacağı dosya bulunmuyordu. Depoya alındı; içinde değer değil yer tutucu var.

### Postmark taşıyıcısı kurulu değildi *(05.08.2026)*

`MAIL_MAILER=postmark` yazılıydı ve `symfony/postmark-mailer` **kurulu değildi** — yani ürünün gönderdiği **her** e-posta gönderim anında patlıyordu: şifre sıfırlama, magic link, bütün bildirimler. Yanında `symfony/http-client` de gerekiyor; hatayı ancak ilki kurulduktan sonra söylüyor. ⚠ Yerel `.env` anahtarı hâlâ eski adındaydı (`POSTMARK_SERVER_API_TOKEN`); `config/services.php` `POSTMARK_API_KEY` okur ve şablon zaten doğrusunu söylüyordu. ⛔ Paketleri çekerken **yüksek** seviyeli bir Guzzle açığı göründü (CVE-2026-69246) ve güncellemeyle kapandı.

### Denetim raporu envanter değildir *(05.08.2026)*

§5.1 "hiç yazılmamış yirmi ekran" diyordu; koda karşı ölçüldüğünde **on sekizi yazılmıştı**. §13.4'ün on sidebar bulgusunun **yedisi** kapalıydı. Rapor 03.08 tarihli bir *anlık görüntüdür* ve o günden beri dört kuşak kapandı. **Kural: bir bulguyu kapatmadan önce koda sor** — aksi hâlde ya yapılmış iş tekrar yapılır, ya da "kapandı" denen şey hiç bakılmamış olur.

### Tekrar eden hata: var olanı ikinci kez yazmak *(05.08.2026)*

Bu oturumda **üç kez** oldu: kayıtlı görünüm tablosu (`staff_query_views` vardı, `table_views` yazdım), `Sparkline` (`charts/Sparkline.vue` vardı — çizgi; benimki çubuk, `TrendBars` olarak yeniden adlandırıldı), ve az kalsın `HorizontalBarChart` (dağılım çubuğunu elle yazacaktım, zaten vardı). ⛔ **Yeni bir bileşen ya da tablo yazmadan önce `ls` ve `grep`.** İki dosya bir kavramı taşıyorsa, ikisi de yarım kalır.

### Tarayıcı turu ve demo veri seti — kararlar *(Sets, 03.08.2026)*

Uçtan uca denetim kod okumasıyla ulaşılabilecek yerin sonuna geldi; **hiçbir ekran tarayıcıda açılmadı**. Sets'in kararı: Playwright ile eksiksiz bir tur atılacak ve bu tur kalıcı bir suite bırakacak.

| Konu | Karar |
|---|---|
| Oturum | **Gerçek akış + TOTP** — demo ofis kullanıcılarına sabit TOTP sırrı kaydedilir, Playwright kodu test içinde hesaplar. Üretim koduna test kapısı açılmaz |
| Çıktı | **Kalıcı suite**, repoya commit, **TeamCity'de ticket kapanışında koşar** |
| Kapsam | Her ekran × **375 + 1440**; sorunlu bulunan ekran dört breakpoint'in hepsinde |
| Kırıklar | **Yalnız kataloğa yazılır** — tur sırasında hiçbir şey düzeltilmez |
| Detay ekranları | ⛔ **Listeden tıklanarak, ve HER SATIR gezilir** *(Sets, 03.08.2026: "tüm detayların çalıştığından emin ol, sonuçta tutarsızlık arıyoruz")*. Örnekleme yetmez: aranan hata "14'ü açılıyor, biri açılmıyor" biçiminde görünür |

⛔ **Sıra: önce doğrula ve düzelt, SONRA yeni ekran** *(Sets, 03.08.2026)*. Playwright turu **var olan** yapıyı eksiksiz doğrular, çıkan bulgular kapatılır, ve ancak ondan sonra tasarım `18` (Sözleşmeler) yeni iş olarak yazılır. Gerekçe: *"18'i şimdi yaparsak geçmişten gelen hatalı şeyler varsa onları da devralmış oluruz. Tüm fixleri yaptıktan sonra hayata geçirirsek eksiksiz tek seferde hallederiz."* Tasarımcı `18`'i teslim etti (`design/18 Sozlesmeler.dc.html`, 03.08.2026) ve **henüz okunmadı** — protokol gereği uygulanmadan önce baştan sona okunacak.

⛔ **Detaya adres yazarak gidilemez — `OpaqueId` (doğrulandı 03.08.2026, 72 dosyada kullanılıyor).** Token, id'nin **oturum anahtarıyla** şifrelenmiş hâli; aynı kayıt iki kişide iki farklı adres, başka oturumun token'ı çözülmez. Suite bu yüzden sabit URL listesiyle gezemez: **liste ekranı açılır, satırların bağlantıları okunur, her biri sırayla ziyaret edilir.** Bu bir kısıt değil avantaj — listeden detaya geçişin kendisi de doğrulanmış olur.
| Portal girişi | Tur öncesi tasarım `10`'un eksik ekranları yazılır (`panel.<domain>/giris`, şifre sıfırlama, magic link halleri, `50`'nin `/k/{token}` karşılaması) — zaten Kuşak 0'da |
| Belge görselleri | **Yerelde sentetik üretilir** (A4 PDF + JPG, "DEMO" filigranlı, biri bulanık). Stok siteden indirme reddedildi: lisans belirsizliği, CI'da ağ bağımlılığı, determinizm kaybı |

⛔ **Kabul ölçütü — tasarımcı kaynağı** *(Sets, 03.08.2026)*: *"tasarımcı eksiksiz bir katalog ve uçtan uca tasarım oluşturdu… tasarımcı tarafından üretilmemiş herhangi bir öge olmaması gerekir sayfada."* Yani tur, ham kontrol aramaz — **her öğenin kaynağını sorar**. Bir öğe ya katalogdaki (`00`–`06`) bir bileşene ya da o sayfanın kendi mockup'ına dayanır; dayanmıyorsa bulgudur.

### Demo veri seti — kapsam *(Sets, 03.08.2026)*

**Production seed'ine giren** (müşteri kurulumunda da gider):
- ⛔ **Eğitim katalogu ZATEN GERÇEK — düzeltme (03.08.2026).** Denetim turunda *"`TrainingDefinitionSeeder` 32 satır demo veri, production'a girecek durumda"* diye raporlandı. **Yanlış.** Seeder açılıp veritabanına karşı sayıldı: 32 satırın tamamı resmî kaynaklardan geliyor — **9 DGY** (docx Ek A / DG-2020 Ek-3), **13 IMDG görev eğitimi** (docx Ek B / YÖN-2019 Ek tablosu) ve **10 TY-2019 Ek-5/2 kod eğitimi**. Süreler, yenileme periyotları (11 ve 12 için 60 ay dahil) ve kapsama kuralları (G04→G02+G03, G08→G05+G06, G09→G07, G13→G09+G04) dokümanla birebir tutuyor. Docblock'taki *"DEMO DATA"* ibaresi *"3E ilk demodan sonra kendi kataloğunu ekrandan düzeltir"* anlamındadır, uydurma satır anlamında değil.
  ⚠ Dolayısıyla **katalog değiştirilmeyecek** — değiştirmek 10 TY-2019 satırını silmek olurdu. Kalan iş yalnız **zenginleştirme**: mevzuat metinlerinin eğitim dokümanı olarak bağlanması ve IMDG/TY ailelerinde boş bırakılmış müfredat konuları.
- **Yetki şablonları genişletilir**: Operasyon direktörü (tam yetki), Muhasebe (mali görür), Satış yöneticisi, Satışçı (mali görmez), Planlamacı, Eğitmen.
- Eğitim dokümanı olarak **mevzuat metinleri** bağlanır (sonradan zenginleştirilebilir).
- ⛔ **Fiyatlar production'a girmez.**

⛔ **Demo veri GERÇEK AKIŞTAN geçer** *(Sets, 03.08.2026)*. Ofis personeli, müşteri firmaları ve eğitimlerin tamamı ürünün kendi action'larıyla üretilir — doğrudan tabloya yazılmaz. Gerekçe: audit satırları, davet kayıtları, tenant provizyonu ve durum geçişleri gerçek olsun; zincirin kendisi de böylece test edilmiş olsun.

⚠ **Tek istisna bakanlık bildirimi:** entegrasyon yok, o yüzden seeder "bildirilmiş gibi" bir KUMS kodu yazar. Geri kalan zincir (planla → yayımla → başlat → yoklama → sınav → tamamla → sertifika) gerçekten koşar.

⚠ **Hem tek eğitim hem program** üretilir — ikisinin ekranlardaki davranışı ayrı ayrı görülebilsin diye.

**Sıra** *(Sets, 03.08.2026)*: müşteriler kurulur → zincir üzerinden programa dahil edilir → birkaç teklif eklenir, **onaylı ve onaysız** → mail, onay ve sözleşme akışları bu veriyle denenir.

⛔ **Onaylı teklifler PORTAL akışından geçer** — müşteri kullanıcısı `ApproveQuote`'u çağırır; okuma kilidi, sürüm hash'i ve delil kaydı gerçek olur. Ofisten işaretlemek hızlı olurdu ama müşterinin gördüğü kritik ekranı (`ScrollToAccept`'li teklif onayı) veriyle beslemezdi. Onaysızlar farklı durumlarda bırakılır — taslak · gönderildi · süresi doldu · reddedildi — ki statü rozetlerinin hepsi ekranda görünsün.

⚠ **§10.1 kapatıldı (03.08.2026):** açık eğitimde `training_customers` boş kalıyordu ve tenant kapsamını oradan türeten dokuz sorgu boş küme alıyordu. Artık hem `EnrolOwnParticipant` hem `EnrolParticipant` firmayı bağlıyor (`syncWithoutDetaching`). Testler: `katilimci` 26/26 · `musteri` 130/130 · `planlama` 229/229.

**Yalnız local/testing demo seeder'ında:**
- Her yetki grubundan **5'er ofis personeli** (şifre `Passw0rd`, sabit TOTP sırrı).
- **10 müşteri firma × 20 personel**, yenileme tarihleri tüm uyarı bantlarını (90/60/30/7/geçmiş) ve 3 ay kuralını görünür kılacak şekilde dağıtılır.
- Eğitimler: **tamamlanmış · açık · kapalı (davet usulü)**, hem **program** hem **tek eğitim** olarak.
- İçerik paketleri + fiyatları, müşteri fiyat grupları ve fiyat listeleri rakamlarıyla.
- Eğitmenler: belgesi tam / eksik / süresi dolmuş.
- Sentetik belgeler her iki tarafta da (ofis personeli ve müşteri personeli).

### Sözleşme yönetimi — yapı kurulacak *(Sets, 03.08.2026)*

Hukuki doküman seti hazırlandı: `docs/legal/` (12 metin + README). ⛔ **Tamamı taslaktır, avukat incelemesinden geçmeden yayımlanmaz.** md dosyaları seeder yazılabilsin diye var; **çalışma zamanında her şey veritabanından okunur.**

⚠ **Bugünkü yapı yetersiz** (denetim, 03.08.2026): `consent_texts` + `consents` yalnız dar bir katılımcı rıza mekanizması. Eksikler: kitle kolonu yok (5 ayrı aydınlatma metni tek `key` ile ayrışıyor) · onay **yalnız `customer_user_id`** için kaydedilebiliyor, yani **ofis personeli ve eğitmenin onayı hiçbir yere yazılamıyor** · yeni sürümde tekrar onay mekanizması yok · metin türü ayrımı yok (aydınlatma / açık rıza / kullanım koşulu / çerçeve sözleşme dört farklı semantik) · firma düzeyi sözleşme kaydı yok · rıza geri çekme yok (KVKK m.7).

**Kurulacak yapı:** `legal_documents` (tür · anahtar · kitle · başlık) → `legal_document_versions` (sürüm · gövde · `effective_from` · `published_at` · `supersedes_id` · `requires_acceptance`) → `legal_acceptances` (kimlik alanı bağımsız kişi bağı · sürüm · an · IP · `withdrawn_at`) + `legal_agreements` (tenant düzeyi çerçeve sözleşme). Mevcut `consent_texts`/`consents` bunun özel hâli; göç edilir.

| Karar | Sets, 03.08.2026 |
|---|---|
| Neyi zorlarız | **Yalnız onay gerektirenler.** Aydınlatma metinleri zorlamaz; portalda **"Sözleşmeler"** sayfasında yaşar (brief: `docs/design/sozlesmeler-brief.md`) |
| Yeni sürüm çıkınca | ⛔ **Kullanıcı durdurulur.** Force logout ile aynı mantık: sözleşme karşısına çıkar, onaylamadan devam edemez. *"Onaysız içeride tutmayalım yasal sorumluluk olan evrakta."* |
| Çerçeve sözleşme | **İkisi de desteklenir** — portalda elektronik kabul VEYA ıslak imzalı nüsha yüklenir. Gerekçe metnin kendisinde: §4 *"ıslak imza veya portal üzerinden elektronik kabul"*, §10.4 portal işlem günlüğünü HMK m.193 delili sayıyor |
| Sözleşme yokken teklif onayı | **(b) Engellenmez, tek akışta birleşir** — teklif onay ekranında çerçeve sözleşme de aynı anda kabul ettirilir. Hukuki sonuç engellemekle aynı (sözleşmesiz sipariş yok), ama müşteri *"önce şunu imzalayın, sonra gelin"* duvarına çarpmaz |

### ⛔ İki ayrı sistem: site sözleşmeleri ve ticari sözleşmeler *(Sets, 06.08.2026)*

*"Normal site kullanım ve KVKK süreci ile teklif sürecini birbirine sokmamak lazım. Aynı engine'i kullanabilirler ama veritabanı tablosu bile ayrı olan şeyi tek potada birleştirmeye çalışmak kaos yaratır."*

| | **Yapı A — site sözleşmeleri** | **Yapı B — ticari sözleşmeler** |
|---|---|---|
| Kapsam | KVKK aydınlatma · açık rıza · kullanım koşulu · politika | çerçeve sözleşme · **NDA** · teklif bazlı diğer sözleşmeler |
| Kim görür | **her kullanıcı kendi onaylarını** | **yalnız yetkili muhatap** |
| Düzey | kişi | firma (tenant) |
| Sona erme | KVKK m.7 geri çekme | fesih — ayrı süreç, bu sistemde yok |
| Tablolar | `legal_documents` · `legal_document_versions` · üç kabul tablosu | kendi tabloları, **teklif dikeyiyle birlikte** |
| Ekranda | Sözleşmeler sayfasının ana listesi | **ayrı kart** — yetkisi olana ve sözleşmesi olana |

⚠ **Gerekçe fiziksel ayrım:** ileride NDA ve başka teklif bazlı sözleşmeler geldiğinde onlar da ikincil sistemden yönetilir. Böylece "genel geçer" site sözleşmeleri ile "ticari" sözleşmeler sistemde ayrı durur.

⚠ **Aynı engine'i paylaşabilirler** — sürümleme, okuma kilidi, kabul kanıtı aynı desenlerdir. Paylaşılmayan şey tablodur.

⛔ **Tasarım `18`'den bilinçli sapma:** A2 tablosu çerçeve sözleşmeyi kullanım koşulunun yanında bir satır olarak çiziyor. Uygulanacak olan yalnız A5'teki **ayrı kart**; ticari sözleşme herkesin gördüğü listeye girmez.

⛔ **Islak imza yolu KİLİTLEMEZ ve tek kapılı değildir** *(Sets, 03.08.2026)*:

- **Evrak backoffice'ten üretilir** — antetli, sürümlü; teklif çıktısındaki antet motorunun aynısı.
- **Yükleyen üç taraf olabilir:** müşteri (portaldan indirir, imzalar, yükler) · **satışçı** (ziyarette imzalı nüshayı alır, ofis tarafından yükler) · operasyon.
- ⚠ *"Kitlemeyecek şekilde kurgulayalım"* — sözleşmenin beklemede olması hiçbir ekranı kapatmaz; yalnız teklif onayı anında kabul istenir.

⚠ **Çerçeve sözleşme nedir:** 3E ile her müşteri **firması** arasında bir kez imzalanan ticari sözleşme — sitenin kullanım koşulu değil, 3E'nin bir kuruma verdiği resmî evrak da değil. Münferit eğitimler bu çatının altında portalda onaylanan tekliflerle sipariş olur. Kişi düzeyinde değil **tenant düzeyinde** kayıttır.

✓ **Kapandı** *(Sets, 06.08.2026 — teyit)*: çerçeve sözleşme yokken teklif onayı **engellenmez, tek akışta birleşir** (yukarıdaki tabloda 03.08'de karara bağlanmıştı; bu satır o kararın altında "açık soru" olarak kalmıştı ve 06.08'de aynı soruyu ikinci kez sordurdu).

⛔ **Tasarım `18 Sozlesmeler.dc.html` bu kararı BİLMİYOR.** Dosyanın içinde hâlâ *"AÇIK KARAR · SETS'E — çerçeve sözleşme olmadan teklif onayı · önerimiz: engellensin"* kutusu duruyor. Tasarım dosyaları güncellenmiyor; çelişkide **bu harita ve brief §8 esastır**. Uygulanacak olan birleşik akıştır.

### ✅ Yazıldı — müşteri tarafı *(09.08.2026, EGT-80)*

Yapının tamamı 06.08'de duruyordu; eksik olan **kapı ve ekranlardı**. Firma yetkilisi portala girip kullanım koşullarını hiç görmüyordu — ofis personeli görüyordu, katılımcı görüyordu, o görmüyordu.

| Ne | Nerede |
|---|---|
| Onay kapısı (Ekran B) | `RequireCustomerLegalConsent` + `OutstandingCustomerConsents` · `Portal/LegalConsent.vue` |
| Sözleşmeler sayfası (Ekran A) | `/sozlesmeler` · `CustomerContracts` · `Portal/Contracts.vue` |
| Geri çekme / yeniden verme | `WithdrawCustomerLegalConsent` · `GiveCustomerLegalConsent` |
| Sürüm farkı deseni | `Domain/Legal/ArticleDiff` · `organisms/LegalVersionDiff.vue` · `legal_texts.articles` kolonu + backoffice repeater |
| Çerçeve sözleşme, teklif onayında | `FrameworkAgreement` · `AcceptFrameworkAgreement` · `ApproveQuote` (tek transaction) |

Kapsama matrisi: `docs/reviews/tasarim-18-kapsama.md` — 82 öğenin 72'si, dört bilinçli sapma, yedi soru.

**Sets'in 09.08.2026 cevapları** (handoff'un üç sorusu):

| Soru | Cevap |
|---|---|
| Çerçeve sözleşme kimi bağlar | **Firma.** Yetki `customer.approve_quote` — ayrı izin tanımlanmadı. Bir yetkili kabul eder, firma bağlanır; ikinci yetkili tekrar sorulmaz |
| Kabul edilmemişse | **"Kabul et ve devam et."** Teklif onayının içinde gösterilir, aynı hamlede iki kayıt düşer. Tasarım `18`'in "engellensin" önerisi uygulanmadı |
| Geriye dönük kabul | **Yok** — sistemde henüz gerçek müşteri yok |

⛔ **KVKK m.7 ile kapı arasındaki ayrım kodda yazılı:** geri çekilmiş bir açık rıza kapıda **tekrar sorulmaz**. Ofis sorgusu geri çekilmişi "bekliyor" sayar — orada metinler kullanım koşuludur ve geri çekilemez, yani durum hiç oluşmaz. Burada oluşur ve tekrar sormak, hakkı kullanılamaz hâle getirirdi.

✅ **Video kayıt açık rızası bağlandı** *(Sets, 09.08.2026: "katılımcı ekranında")*. Katılımcı ekranında **ikinci** bir blok: `GiveRecordingConsent` → `customer_agreements`, geri çekme `WithdrawCustomerAgreement`, sözleşmeler sayfasından geri çekilir/yeniden verilir.

⛔ **İki rıza, iki kayıt, tek ekran.** Aydınlatma metni bir SİTE metnidir (`legal_texts`) — katılımcı ekranlarını kullanan herkes okur. Kayda alınmak İLİŞKİDEN doğar: 3E o firmanın insanlarını eğitiyor ve çekiyor, dolayısıyla ticari metindir ve `customer_agreements`'a düşer. Kişi ikisini aynı ekranda görür; birleşmeyen şey **kayıttır** — biri herkese söylenen bildirim, diğeri KVKK m.7'nin geri aldırdığı açık rıza; tek satır ikisini birden taşıyamaz.

### ✅ Satışçının imzalı nüsha ekranı *(09.08.2026, EGT-81)*

⛔ **Tasarımcı beklenmedi** *(Sets: "katalogda zaten her şeyimiz var dosya yüklemeyle alakalı… tasarımcıya gerek var mı sence?")*. Gerekçe ölçüldü: aynı ekranın ikizi tedarikçi tarafında yazılı (anlaşma kartı + durum rozeti + imzalı nüsha + "kopya kritiktir"), katalogda eksik atom yok.

| Ne | Nerede |
|---|---|
| Müşteri kartında sözleşme bölümü | `Office/Customers/Show.vue` — rozet, yöntem, imzalayan, yükleyen, nüsha bağlantısı |
| Yükleme | `RecordSignedFrameworkAgreement` · `POST /musteriler/{customer}/cerceve-sozlesme` |
| Nüshayı okuma | `GET …/nusha` — bizden geçer, `AccessLogger` ile loglanır, `private, no-store` |
| Şema | `customer_agreements`: `method` · `signatory_name` · `recorded_by` · `attachment_*`; `customer_user_id` **nullable** oldu |

⛔ **`customer_user_id` neden nullable:** kâğıdı imzalayan kişi genellikle portal kullanıcısı değildir — bu yolun var olma sebebi tam olarak budur. Yerine sahte bir kullanıcı satırı açmak, firmanın personel listesine hiç giriş yapmamış birini koymak olurdu. İki CHECK kısıtı boşluğu kapatıyor: **portal kabulü kişiyi**, **kâğıt kabulü imzalayanı** adlandırmak zorunda.

⛔ **Firma adı YAZILIR, işaretlenmez.** Brief'in istediği "yanlış firmaya yüklemeyi zorlaştıran teyit" bir onay kutusu olarak yazılsaydı işe yaramazdı — kutu, insanların işaretlediği şeydir. Ad yazılıp `TurkishFold` ile karşılaştırılıyor; on beş kartı açık bir satışçının yanlış firmanın sözleşmesini dosyaladığı an, ekranda hiçbir şey yanlış görünmüyor.

Kapsama matrisi: `docs/reviews/brief-a5b-kapsama.md` — 18/18, sapma yok.

### ✅ Antetli çıktı ve müşterinin kendi yükleme yolu *(09.08.2026, EGT-82)*

⛔ **İkisi tek işti.** Çıktı olmadan "imzalı nüshayı yükle" bir çıkmaz sokak — imzalanacak kâğıt hiçbir yerde üretilmiyordu; yükleme olmadan da çıktı bir çıkmaz sokak. Brief §3'ün üç yükleyicisinden **üçü de** artık çalışıyor.

| Ne | Nerede |
|---|---|
| Sözleşmenin kâğıt hâli | `FrameworkContractPrintout` + `organisms/ContractDocument.vue` — **tek belge, iki host**; ofis ve portal aynı sayfayı basar |
| Satışçının ziyarete götürdüğü kâğıt | `/musteriler/{customer}/cerceve-sozlesme/yazdir` |
| Müşterinin bastığı kâğıt | `/sozlesmeler/cerceve/yazdir` |
| Müşterinin kendi yüklemesi | `/sozlesmeler/cerceve/nusha` · `FileFrameworkAgreementCopy` |

⛔ **Antet ayrı bir tür DEĞİL:** `DocumentTemplate::QUOTE_LETTERHEAD` satırı 3E'nin antedidir, adı yalnız onu ilk isteyen belgeden gelir. İkinci bir tür açmak, evin aynı görseli iki kez yüklemesi ve birini unuttuğu gün sözleşmenin antetsiz basılması demekti.

⛔ **PDF üretilmez, sayfa basılır** — projedeki tek PDF yazıcısı Türkçeyi bozuyor ve birinin **imzaladığı** belgede bozuk firma adı, PDF'siz olmaktan kötüdür.

⚠ **Verilmemiş tek karar:** müşterinin **imzalanmış nüshayı geri indirmesi**. Sözleşme metni yazdırılabiliyor; imzalı taramayı firmanın her personeline açan bir bağlantı yok.

⛔ **Brief'in A5-b'si çizilmedi.** `docs/design/sozlesmeler-brief.md` §3 açıkça *"Ofis tarafı karşılığı — satışçı ziyaretten imzalı nüshayla döndüğünde yükleyeceği yer: durum rozeti · İmzalı Nüshayı Yükle · kim/ne zaman yükledi izi · yanlış firmaya yüklemeyi zorlaştıran firma adı teyidi"* istiyor. Tasarım `18` yalnız müşterinin gördüğü A5 kartını çizmiş; `32 Satisci Musteri Karti`'nda da sözleşme bölümü yok. **Satışçının yükleme ekranı tasarımcıdan istenecek** — üç yükleyiciden biri o (Sets, 03.08 ve 06.08).

### ✅ `office_agreements` bacağı — 3E'nin kendi insanları *(09.08.2026, EGT-91)*

**Sets, 09.08.2026:** *"gizliliği zaten kapıda alalım, istihdam sözleşmesi yüklenebilsin ve task olarak açılabilsin."*

| Ne | Nasıl |
|---|---|
| Gizlilik taahhütnamesi | **Kapıda.** `OutstandingOfficeAgreements` + `GiveOfficeAgreement`; ofis kapısı artık **iki sistemi tek kuyrukta** taşıyor |
| İstihdam sözleşmesi | **Belge + task.** `document_types`'a `ISTIHDAM-SOZLESMESI` ve `SGK-BILDIRGESI` eklendi |

⛔ **Neden ikisi farklı yolda:** gizlilik taahhütnamesi 3E'nin kendi erişim şartıdır — *"bunu gizli tutacak mısın"* sorusunun doğru cevabı bir kapıdır. İstihdam sözleşmesi iki taraflı ve **İdare'ye kâğıt olarak ibraz edilen** bir belgedir (Y-2016 md.11/1-e); tıklanması bir denetimin görmek istediği şey değildir.

⛔ **İki tablo, tek kapı.** Site metinleri ve ticari metinler kendi tablolarında kalıyor (Sets, 06.08.2026) ama kişi tek kapıyla karşılaşıyor: art arda iki kapı, birincisini okumadan ikincisini fark etmeden onaylayan bir insan demektir. Ekran satırın **kaynağını** geri gönderiyor — iki id de tamsayı, tahmin eden bir sunucu er geç yanlış belgeye onay yazar.

⚠ **Ölçülmüş iki bulgu:**
1. Mevzuat *"eğitici başına: eğitici belgesi, **istihdam sözleşmesi**, SGK bildirgesi, kimlik fotokopisi"* diyor; ilk ve son ikisi vardı, ortadaki ikisi **hiç yoktu**. Firma sistemin istediği her evrakı tamamlayıp yine de İdare'nin isteyeceği belgeden yoksun kalabiliyordu.
2. Önce bir `instructor_requirements` satırı yazıldı ve **geri alındı**: o satırlar eğitim tanımı üzerinden bağlanır, dolayısıyla hiçbir yerde görünmezdi. Doğru taşıyıcı `is_critical` — `RequirementMap::universal()` tam olarak eğitmen kapsamlı kritik türleri *"her eğitmenden istenen"* diye çiziyor.

### ⛔ Operasyon direktörü SÜPER KULLANICIDIR *(Sets, 03.08.2026)*

*"Operasyon direktörü 'super user' yetkisi. Haliyle tüm sayfaları, tüm gündemleri, tüm her şeyi — **eğer backoffice'ten yetki verildi ise** — görebilir, düzenleyebilir. Bunu unutma."*

Yani yetkinin **kaynağı** backoffice'tir; ama yetki verildiğinde ofis tarafında kapalı kalan hiçbir kapı olmamalıdır.

⚠ **Bugünkü kod bunu sağlamıyor.** Denetim §6.2: `/egitmen/*` grubundaki 17 route'un kapısı izin değil, `Instructor` kaydının varlığıdır (`InstructorPanelController.php:604-611`); kaydı olmayan **404** alır. Hiçbir izin bu grubu açamaz — dolayısıyla operasyon direktörü eğitmen panelini, eğitmen gündemini, müsaitlik ve cari ekranlarını göremiyor.

Bu bir yetki paketiyle çözülmez; **kapının kendisi değişmelidir**. Kapanış işi Kuşak 1'e yazıldı: eğitmen paneli route'ları "kendi kaydı VEYA yeterli izin" kapısına geçer, kendi kaydı olmayan direktör için ekranlar okuma kipinde açılır.

⚠ Aynı kural gündemler için de geçerli: `/yonetim` ve `/onaylar` bugün **sıfır yetkiliye açık** (denetim §2.5) — yani ters yönde bozuk. Doğrusu: kapı izinle korunur, operasyon direktörü izni taşıdığı için girer.


⛔ **EGT-41 kararları — Zoom kaydının otomatik YouTube'a gitmesi** *(Sets, 20.10.2026)*: (1) Eşleştirme: **oturuma Zoom toplantı kimliği girilir**; kayıt bitince sistem o toplantının kaydını çekip YouTube'a liste dışı yükler ve linki oturuma yazar (`RecordSittingRecording` aynı eylem, yalnız link kaynağı değişir). (2) **Önce kurulum kılavuzu, sonra kod**: Zoom uygulaması ve Google/YouTube yetkilendirmesi 3E hesabında açılıp uygulama türü gerçek hesapta doğrulanmadan kod yazılmaz; kılavuz `docs/infra/zoom-youtube-kurulum.md`.

### ⛔ Video kayıt akışı KAPSAMDA — gözden kaçmış *(Sets, 03.08.2026)*

Çatı §84-89 ve AD-13 eğitim kaydının Zoom'da alınıp **unlisted YouTube**'a yüklenmesini ve linkin **yalnız İdare ile** paylaşılmasını tarif ediyor. Uçtan uca denetim kod tabanında **tek satır** bulamadı (`zoom|youtube` grep'i yalnız `DocumentPreview.vue`'nun yakınlaştırma kontrolüne düşüyor).

Sets'in cevabı: **"gözden kaçmış."** Yani ertelenmiş bir karar değil, atlanmış bir iş.

⚠ İki yan sonucu var: (a) Bakanlığın kayıt talebi karşılanmıyor; (b) `ConsentText.covers_recording` bayrağı **var olmayan bir işlem için rıza alıyor** — rıza metni kapsamadığı bir şeyi kapsıyormuş gibi duruyor.

**CI durumu:** build **#811 SUCCESS**, 917 test — `main` üzerindeki her şey doğrulanmış durumda (29.07.2026).

---

## 3. Doküman haritası

| Dosya | Ne cevaplar | Otorite |
|---|---|---|
| `cati-dokumani.md` | İş kuralları, süreçler, roller — **tek kaynak** | İş kuralı sorusunda esas |
| `docs/arch/mimari-kararlar.md` | AD-01…AD-28: stack, tenancy, audit, auth, yetki, evrak, mevzuat | Mimari sorusunda esas |
| `docs/arch/kod-yerlesimi.md` | Hangi kod nereye yazılır (AD-25 uygulaması) | Kod yazmadan önce |
| `docs/mevzuat-kurallari.md` | Madde referanslı kural seti | Mevzuat sorusunda esas. **"MEVZUATTA YOK" satırları varsayımla doldurulamaz** |
| `mevzuat/` | Resmî metinler (envanter: `mevzuat/README.md`) | `mevzuat-kurallari.md` bunun çıkarımıdır |
| `docs/specs/*.md` | Modül spec'leri | Heyet onaylı olanlar bağlayıcı |
| `docs/plans/*.md` | Implementasyon planları | Görev sırası ve kabul kriterleri. Açık olanlar: `musteri-yonetimi-plan.md` (EGT-62), `planlama-t1-plan.md` (EGT-63), `planlama-t2-plan.md` (EGT-64) |
| `design/HANDOFF.md` + `design/*.dc.html` | Tasarım sistemi ve ekran haritası | ⛔ **Eksiksiz uygulanır** — token ve bileşen dili kadar sayfa kompozisyonu, alan sıralaması ve ekran bölünmesi de. Sapma yalnız Sets söylediğinde; prosedür `CLAUDE.md` → Tasarım Uygulama Protokolü |
| `docs/infra/ortamlar.md` | Sunucular, TeamCity, YouTrack, PgBouncer | Ortam sorusunda |
| `docs/issues.md` | Çözülmemiş belirsizlikler, kayda geçmiş teknik borçlar | |
| `docs/reviews/` | Heyet review çıktıları | |
| `CLAUDE.md` | Çalışma kuralları, kırmızı çizgiler, kalite kapıları | Mutlak |

**Spec durumları:** `omurga-spec.md` (v1.12, uygulandı) · `egitmen-spec.md` (heyet 158 APPROVED, **uygulanmadı** — yalnız eğitmen kaydı T4'te yapıldı) · `ofis-onyuz-spec.md` (v1.5, T1/T2 uygulandı) · `fiyatlama-spec.md` (uygulandı: `prices`, `price_lists`, `price_groups`, `price_bands`)

---

## 4. Alanlar

### 4.1 Backoffice

**Ne yapar:** sistem verisini üretir — mevzuat kaydı, eğitim kataloğu, evrak türleri, parametreler, fiyat tabloları, bildirim şablonları, rıza metinleri, ofis kullanıcısı provisioning. AD-09: **backoffice üretir, ofis tüketir.**

**Durum:** omurga ✓ · ekranlar ✓ (Filament) · chunk 10 kapandı (EGT-53)

| Karar | Kaynak |
|---|---|
| Backoffice ayrı kimlik alanıdır; `users` ile hiçbir tablo paylaşmaz — özellikle yetki tabloları | `omurga-spec.md` §3 |
| Backoffice'in yetki tavanı yoktur; şirket sahibi varsayımına dayanır | `omurga-spec.md` §3.3, risk kaydı `docs/issues.md` |
| Parametre daraltma ofiste serbest, **genişletme onaya gider** *(revizyon 28.07.2026 — eskiden hataydı)* | AD-09 |
| Mevzuat kaydı kural değil **uyarı** üretir; sayılar eğitim tanımının alanıdır | AD-27, `mevzuat-kurallari.md` §11 |
| Sertifikayı Bakanlık üretir; 3E şablonlamaz, saklamaz | AD-28 |

**Veri:** `regulations`, `regulation_versions`, `regulation_advisories`, `regulation_documents`, `training_definitions`, `training_curricula`, `training_topics`, `training_coverage`, `instructor_requirements`, `training_definition_instructor_requirement`, `document_types`, `document_type_scopes`, `option_lists`, `option_list_values`, `settings`, `notification_templates`, `consent_texts`, `price_*`

**Ekranlar:** Filament — `Regulation`, `TrainingDefinition`, `InstructorRequirement`, `DocumentType`, `OptionList`, `NotificationTemplate`, `ConsentText`, `PriceList`, `PriceGroup`, `PriceBand`, `BackofficeUser`, `OfficeUser`, `Group`

**Ticket:** EGT-53 (chunk 10) ✓ · EGT-22 (spec — kapsamı büyük ölçüde karşılandı)

**Açık:** eğitim kataloğunda `instructor_requirement` serbest metin alanı hâlâ duruyor — yapılandırılmış şart pivotu geldiği için artık gereksiz olabilir, temizliği planlama turunda değerlendirilecek.

#### ⛔ Backoffice'in ÖZEL FİLAMENT TEMASI YOK — elle yazılan Tailwind sınıfı derlenmez *(13.08.2026, EGT-123)*

Bu panel yalnız **Filament'in hazır derlenmiş CSS'ini** yükler. Bir blade'e yazılan `grid`, `gap-6`, `text-3xl`, `h-1.5` gibi sınıflar **hiçbir zaman üretilmez**; tarayıcıya var olmayan sınıf adları olarak gider ve hiçbir şeyi biçimlendirmez.

⚠ Belirtisi kandırıcıdır: markup doğrudur, hata çıkmaz, ekran yalnızca "çirkin" görünür. Kartlar tam genişlikte alt alta yapışır, iri yazılmış rakam küçük kalır, ilerleme çubuğu hiç çizilmez. Aynı tuzak 03.08.2026'da log ekranlarında da yaşandı (`overflow-x-auto` derlenmedi, satır içi `style` ile geçildi) ve 13.08.2026'da pano panelinde **iki tur** kaybettirdi.

**Kural:** bu panelde çizilen her şey Filament'in kendi bileşenlerinden kurulur — `StatsOverviewWidget`/`Stat`, `Section`, `TextEntry` (dizi durumu için `bulleted` + `limitList` + `expandableLimitedList`), `Table`, `Badge`, `Action`. Yetmiyorsa **önce tema eklenir**; blade'e sınıf yazılmaz. *(Yerleşim kuralının kendisi zaten `kod-yerlesimi` §4'te: "custom Blade view yazma".)*

#### ✅ Panoya "Yapılacak işler" paneli *(13.08.2026, EGT-123)*

Beş kontrol, satırda ikişer kart: bildirim metinleri · fiyat bandı · 3E'nin kendi yetki belgeleri · yenileme süresi · firmadan beklenen belge. **Kesir olarak** (`0/9`), üst/alt bölüm olarak değil — hiç boşalmayan bir liste okunmaz hâle gelir.

⛔ **Kart eksikleri ADIYLA ve madde madde yazar** *(Sets, 13.08.2026, Tayfun'un geri dönüşünden sonra)*. Yalnız sayı yazmak yetmedi ve ölçüldü: Tayfun "0/9"u görüp şablon ekranına girdi, orada **var olanları** gördü, hangisinin eksik olduğunu anlayamadı ve sayaçla ilgisi olmayan bir olaya metin yazdı. O ekran hiç yazılmamış olanı gösteremez; karşılaştırmanın yapılabileceği tek yer panodur.

| Karar | Sebep |
|---|---|
| Bedel kartın başlığında, **gri ve tam metin** | Önce ipucundaydı, yanında kısa özet vardı: tek düşünce için iki metin, işe yarayan yarısına yalnız fareyle ulaşılıyordu |
| Eksikler gövdede, **normal metin renginde** | İkisi de uyarı renginde olunca tek bir turuncu paragraf gibi okunuyordu |
| Liste **altı maddede** kesilir, gerisi "N kayıt daha göster" | Bir liste 31 uzunluğunda; hepsini basan kart yanındakini ekrandan itiyordu |
| Bu bir **hata günlüğü değil** | Yalnız bir yöneticinin oturup düzeltebileceği eksikler listelenir. Günlükler, içindekilerin çoğu için yapılacak bir şey olmadığı için okunmuyor |

#### ✅ Bildirim metinlerinin tamamı yazıldı *(13.08.2026, EGT-122)*

⛔ **Sıra tersine çevrildi** *(Sets, 13.08.2026: "biz her senaryo için mail template'lerini hazırlasak, sonuçta yazılacak cümleler ve kalıplar belli")*. EGT-121'in kanal kararı beklenmeden 33 olayın **65 metni** (32 mail + 33 uygulama içi) yazıldı ve yayınlanmış olarak konuldu. Bir bildirimi açmak artık tek karar; karar artı bir öğleden sonralık yazı işi değil.

⛔ **Metin yazmak gönderim AÇMAZ** *(Sets: "aktif edilmediği sürece zaten bildirim gitmeyecek")*. Gönderimi açan şey bildirim **kuralıdır**; kural yoksa `AssignNotifications` hiçbir alıcı çözmez. Kodda doğrulandı.

⛔ **Seeder var olan metne DOKUNMAZ**, taslak dahil. "Varsayılana döndürmek", birinin yarım kalmış düzeltmesini bir sonraki deploy'da sessizce silmenin en kolay yoluydu.

| Yapı | Karar |
|---|---|
| **Olay sözlüğü** (`NotificationEvent::variables()`) | Metni yazan kişi programcı değil ve tanımadığı yer tutucu alıcıya **aynen** gider. Adlar tekilleşti: referans kodu için dört ad vardı (`code`, `reference_code`, `invoice`, `certificate_no`), eğitim adı için iki, geri sayım için iki |
| **`{{ recipient }}` ≠ `{{ person }}`** | Okuyan ile hakkında konuşulan ayrı. Sertifika uyarısı sıklıkla satışçıya gider; tek kelime ikisini karşılasaydı mail satışçıya müşterinin personelinin adıyla "Merhaba" derdi |
| **`{{ customer }}` dinleyicide çözülür** | Firma adı tenant kapsamlı ve bildirim tam da kapsam kapandıktan sonra doğuyor. Gönderende okumak yedi testi düşürdü; `AssignNotifications` konuyu yüklemek için o kapsamı **zaten** açıyor |
| **Tarih tarih olarak geçer** | `toDateString()` müşterinin mailine `2026-08-13` yazıyordu; biçim metni yazanın elinde değil |
| **Mail kiti** | İş bildirimleri Laravel'in İngilizce iskeletiyle gidiyordu. Kit EGT-105'te kurulmuş, yalnız üç kimlik mailine bağlanmıştı. Görünmemesinin sebebi: yayınlanmış metin yoktu. ⛔ **Kitin dışında kalan son mektup 18.08.2026'da içine alındı (OPS-19):** yazışma mektubu (teklif, muhasebe bildirimi, hatırlatma) kendi tek satırlık şablonunu Laravel'in `x-mail::message` bileşeniyle çiziyordu ve o bileşenin görünüm ad alanı yalnız `Markdown::render()` içinde kaydolur — yani mektup, aynı süreçte ondan önce bir **markdown** mail çizilmişse çalışıyordu. Canlıda çizilmemişti: teklif hiç gitmedi. Kural: **kitin dışında mail şablonu yok**, ve düz metin yarısı olmayan mail yok |
| **15 ölü tetikleyici** | Katalogda durup hiçbir yerden doğmayan 15 olay bağlandı. Kanal seçilse bile hiçbiri çalışmayacaktı. `content_package.expiring` için yeni bir günlük tarama yazıldı |

⚠ Panodaki **"Bildirim metinleri 0/9" → 9/9**: 183 e-postanın gitmemesinin sebebi tam olarak buydu.

#### ✅ İşlem ve erişim günlüğü gerçek tablo oldu *(13.08.2026, EGT-133)*

*"Loglar düz metin gibi atılmış sayfalara. Ne bir filtreleme var, ne düzgün gösterim."* İkisi de elle yazılmış bir `<table>` idi: en yeni 200 satır, sayfalama yok, süzgeç yok, değerler ham (`App\Models\Quote`, `backoffice_user`, ISO tarih, JSON bloğu).

⛔ **Süzgeçler kendi sorgusunu KURMAZ.** İki tablo da `BackofficeAuditFeed` üzerinden okur — çapraz firma kapsamının açılıp kapandığı ve **okumanın erişim günlüğüne yazıldığı** tek yer orası. Kendi `where`'ini uygulayan bir süzgeç ikinci bir okuma yolu olurdu ve erişim günlüğü ondan habersiz kalırdı. Sayfalama SQL'de (`writePage` / `accessPage`); erişim kaydına yazılan sayı o sayfada **gerçekten görülen** satır sayısıdır — toplamı yazmak 25 kayıt okuyanı 10.000 okumuş gibi gösterirdi.

⚠ "Bitiş" tarihi seçilen günün **sonunu** kapsar; aksi hâlde "13.08'e kadar" demek o günü sakin gösterirdi.

---

### ⛔ Geçmiş veri — İzmir Akademi'nin devralınan eğitimleri *(Sets, 14.08.2026, EGT-108)*

İzmir'de ayrı bir şirket olarak çalışan Akademi'nin geçmişte verdiği eğitimler merkez şirkete devrediliyor. Elde **karmakarışık bir Excel** var ve bu iş onu sisteme sokmanın yoludur.

**Excel'in ölçülmüş profili** *(Tayfun'un `tüm isimler.xlsx` dosyası, 14.08.2026)* — bunlar gerçek veridir, demo değil:

| | |
|---|---|
| Satır / benzersiz kişi | **2.381 satır · 1.040 kişi** — satır kişi değil, **belge**; bir kişi 10 satıra kadar çıkıyor |
| Kimlik | 2.380/2.381 algoritmaya göre geçerli TCKN; **1 bozuk** |
| E-posta | **hiç yok (0 satır)** |
| Telefon | **hepsinde var (2.381)** |
| Firma adı | **yalnız «Şirket» kolonundan gelir**: 1.469 satırda yazılı (64 benzersiz firma), **912 satır firmasız**. Kişi bazında: 1.040 kişinin **565'inin** firması biliniyor. ⚠ «Yer» kolonu firma DEĞİL, **eğitimin yapıldığı yer** (Sets, 14.08.2026) — bu tür eğitimler çoğunlukla firmanın kendi yerinde veriliyor, dolayısıyla orada bir firma adı yazıyor **olabilir ama olmak zorunda değil**. Olduğu gibi saklanır ve eşleştirmeyi yapan insana **gösterilir**; sistem onu firma olarak kullanmaz. «KARIŞIK» değeri de bu kolondadır — karışık bir yerde yapılmış eğitim demek |
| Tarih | eğitim tarihi 2.226 satırda (155 boş), 2024-01-16 … 2026-05-06. Excel'de geçerlilik tarihi hiç yok |
| Belge türü | **36 distinct değer**, gerçekte 8 eğitim (Gözetim · IMSBC KOD · Grain Kod · IBC KOD · CSS KOD · IGC KOD · MARPOL 73/78 · TDC kod). «Y-» öneki yenilemeyi anlatıyor. ⚠ «MARPOL 73/79»dan «73/98»e kadar **21 bozuk varyant** var (Excel'de sürükleyerek doldurulmuş), her biri 1-2 satır |

**Akış — sıra sabittir:**

1. **Önce müşteri doğar, normal süreciyle.** 3E temas eder, firma paneli kullanmak ister, hesap açılır, **müşteri yöneticisi giriş yapar.** ⛔ Bu geçmiş verinin parçası DEĞİLDİR ve geçmiş veri firma açmaz: *"geçerli müşteri olmadan bizim Excel'deki kişileri bağlayabileceğimiz ve onları düzenleyip onaylayacak kimse yok zaten"* (Sets).
2. **Backoffice → «Geçmiş veri» → eğitim adı eşleme.** Excel'deki 36 belge türü **olduğu gibi** listelenir; insan her biri için hem eğitim tanımını hem **ilk/yenileme**yi seçer. ⛔ Sistem «Y- = yenileme» varsayımı kurmaz — önek insana bilgi olarak görünür, kararı insan verir.
3. **Backoffice → ana liste.** Satırlar datatable; firma adı ve TCKN ile aranır, süzülür. İnsan **müşteriyle konuşarak, teyit ederek** personelleri seçer ve bir firmaya **taslak** bağlar. Kişi bu adımda doğmaz.
4. **Müşteri panelinde «3E'den daha önce eğitim almış personelleriniz».** Firma saha/ofis seçer, «eğitim alacak» seçili gelir (zaten eğitim almışlar), **e-posta ve lokasyon** girer — liste geçmişten geldiği için kişi ayrılmış olabilir. Kaydet → kişiler gerçek müşteri kullanıcısı olur, davetler gider.
5. Ana listede o satırlar **doğrulanmış** işaretlenir: artık o kişinin o firmada olduğunu biliyoruz.

⛔ **HİÇBİR OTOMASYON YOK — bu kuralın kendisi karardır** *(Sets, 14.08.2026: "sakın böyle bir şeye kalkışma! varsayımla ilerlersin sonrasında çok ciddi sorunlar yaşarız")*. Sistem eşleştirme önermez, satır seçili getirmez, benzerlik puanı hesaplamaz, «en son eğitimin firması» gibi kural kurmaz. Aynı TCKN iki firmada görünüyorsa (ölçüldü: 33 kişi) ekran bunu **bilgi olarak yazar**, kararı vermez. Ofisten bir backoffice yetkilisi, müşteriyle konuşarak eşleştirir.

⚠ **Arama ve süzme serbesttir, öneri değildir** *(Sets)*. TCKN/ad/firma ile arama, sıralama, «firması bilinmeyenler» süzgeci — bunlar insanın kullandığı araçlardır. Sistemin kendiliğinden işaretlediği hiçbir şey olmayacak.

⚠ **Firmasız havuz kalıcı bir arama alanıdır, çöp değil.** Bugün yaşanmış örnek: firma *"4 kişi eksik"* demiş, listeyi göndermiş, TCKN ile **firmasızlar arasında** bulunmuşlar. **912 firmasız satır** (475 kişi) orada bekler ve talep geldikçe çözülür.

⚠ **E-posta herkes için gerekli** — saha personeli de panele giriyor. `ParticipantAreaController` katılımcı ekranlarını `staff_kind`'a değil **kişinin eğitim kaydına** bakarak açıyor: saha personeli eğitim gününü görür, yoklamasını işaretler, anketini doldurur. EGT-113'teki *"panele erişecekse ofis personeli"* cümlesi firma **yönetim** panelini kastediyor.

⛔ **Bu veri tenant'lı tablolara girmez.** Excel satırları kendi tablolarında (ön oda) durur; kişi ancak müşteri «Kaydet» dediği anda, gerçek firmasıyla `customer_users`'a doğar. Sebep sert: bu üründe her satırın bir tenant'ı var ve satır güvenliği buna dayanıyor — firması belirsiz satırlar o tabloların içinde ya scope'suz sorgu ya sızıntı üretir (kırmızı çizgi 1 ve 4). ⚠ Havuz TCKN taşıyor, yani **maskeleme ve erişim logu** (AD-16, AD-21) burada da geçerli.

**Sertifika tarihleri — ve «doğrulanmamış» rozeti** *(Sets, 14.08.2026)*:

- Excel'deki eğitim tarihi, eğitim tamamlandığı için **sertifikanın düzenlenme tarihi** olarak yazılır.
- **Geçerlilik bitişi**, eğitim tanımının **yenileme periyodundan hesaplanır**. ⛔ Boş bırakılamaz: bugünkü uyum sorgusu, geçerlilik tarihi olmayan bir sertifikayı «hiç dolmayan» sayıp kişiyi listeden düşürüyor — yani boş tarih, takibin sessizce çalışmaması demek.
- ⛔ Ve her geçmiş sertifika bir **rozet** taşır: *«geçmiş veriden geliyor, lütfen bakanlıktan doğrulayın»*. Rozet tek bir ekranda kalmaz — kişi kartı, uyum sorgusu, müşterinin personel listesi, nerede görünüyorsa orada. Teknik karşılığı ucuz: sertifikanın kaynağı **`legacy`** olur (bugün `training` ve `declared` var), rozet o kaynaktan gelir.
- **Sorumluluk müşteridedir:** *"bizdeki eğitim tarihi bu, gerçek sertifika tarihini siz doğrulayın"*. Müşteriden evrakları yüklemesi ve sertifika tarihini doldurması istenir. ⚠ Takip için **kişi başına yalnız son sertifika** yeterli.

⛔ **Ne yükleme ekranı, ne de içe aktarma betiği yazılacak** *(Sets, 14.08.2026)*: *"herhangi bir kod ya da script yaparsak yarın bir tutarsızlık durumunda corrupt ederiz — sen elle yapıyormuş gibi yap daha sağlıklı olur."* Yükleme **oturumda, denetimli** yapılır: dosya okunur, ne yazılacağı önce raporlanır, Sets görür, sonra yazılır. Yalnız **şema** migration ile kurulur; veri asla migration'a gömülmez (içinde 2.381 kimlik numarası olan dosya depoya konmaz). Yakında gelecek **10 yıllık** dosya da aynı yolla: önce fark raporu, sonra yazma.

**Dört ayrıntı da karara bağlandı** *(Sets, 14.08.2026)*: **Tarih2 = eğitimin bitiş günü** — sertifika eğitim bittiğinde düzenlendiği için düzenlenme tarihi Tarih2 varsa odur · backoffice'te **kendi izni** olur (mevcut bir izne yamanmaz, çünkü kimlik havuzunu kimin gördüğü ayrı bir sorudur), müşteri tarafında mevcut personel yetkisi yeterli · **kimlik numarası geçersiz** satır yüklenir, işaretlenir ve firmaya **bağlanabilir**; doğru numarayı müşteri kendi ekranında girer, düzeltmeden kişi kaydedilemez.

**Aynı kişi firmada zaten kayıtlıysa** yeni kişi açılmaz, **mevcut kişiye belge eklenir** *(Sets, 14.08.2026)*.

✅ **Yanlış gelen sertifika firma tarafından YERİNDE düzeltilir** *(EGT-212, Tayfun: "3e den gelen veriler eğer hatalıysa şirket yetkilisi bunu düzenleyemiyor"; karar Sets, 21.09.2026)*. Yukarıdaki *"sorumluluk müşteridedir, sertifika tarihini siz doldurun"* kararının yarısı teslim edilmemişti: rozet «bakanlıktan doğrulayın» diyordu, doğrulama sonucunu yazacak yer yoktu — çekmece salt okunurdu, altındaki tek form yalnız ekliyordu (`DeclareStaffCertificate` docblock'u "düzeltme ayrı bir eylemdir" diyordu, o eylem yazılmamıştı). Karar, üç seçenekten **yerinde düzenle + iz kalsın**: `CorrectStaffCertificate` — eğitim, numara, veriliş, bitiş değiştirilebilir; eski değerler `certificate.corrected` audit'ine aynı transaction'da yazılır; düzelten firma olduğu için belge **`declared`'a döner** (geçmiş veri rozeti kalkar, firma beyanı rozeti gelir). ⛔ `training` kaynaklı (3E'nin verdiği) sertifikaya düğme çizilmez ve uç 403 döner — o ofisin işidir. İptal + yeniden ekle ve "ofis düzeltsin" seçenekleri **reddedildi**.

✅ **İkinci parti: görev 4 ve Kategori 1** *(EGT-214, Tayfun'un `kat1-kat4-kursiyerlistesi.xlsx` dosyası; karar Sets, 22.09.2026)*. 741 satırın **598'i** havuza alındı, **143'ü bilerek dışarıda bırakıldı ve Tayfun'a soruldu**. Dosyanın profili ilkinden farklı: her satırda geçerli kimlik numarası, telefon **ve firma adı** var (firmasız satır yok), e-posta yine hiç yok, **tek tarih kolonu** var — yani `trained_on_2`, «Yer» kolonu ve bozuk kimlik numarası bu partide yok. Firma adı 49 farklı yazımla geçiyor, gerçekte ~35 firma (yalnız Vitsan 6 yazım); kural gereği ham hâliyle korunuyor, eşleştirme yine elle.

⛔ **Şüpheli tarih girilmez, sorulur** — dışarıda bırakılan iki grup bir yargı değil, müşteriye sorulmuş iki sorudur:

| Ne | Kaç | Neden girilmedi |
|---|---|---|
| Eğitim tarihi **bugünden ileri** olan satırlar (01.10.2026 ve 24.10.2026) | 29 | Yapılmamış bir eğitimin sertifikası düzenlenemez; geçerlilik bitişi de o tarihten hesaplandığı için aynı yönde yanlış çıkardı. ⚠ «Kategori 1» (ilk) yazımının **tüm** satırları burada — bu yüzden havuza dosyanın dört yazımından yalnız üçü girdi |
| **Aynı kişi iki kez**, iki gün arayla, aynı yazım ve aynı firma (30 kişi İÇDAŞ «Y-Kategori 1» 24/26.09.2024 · 27 kişi Mardaş görev 4 01/03.12.2025) | 114 | Sınavdan kalıp tekrar girmiş olmak **tek** sertifika demek, iki günlük eğitim de öyle; gerçekten iki ayrı eğitimse **iki**. Bilmeden girmek 57 kişiye ikişer sertifika açardı |

⚠ Yükleme yine seeder üzerinden: `LegacyTrainingRecordSeeder` artık tek dosya değil **dosya listesi** okuyor (`FILES`), her parti kendi `source_file` adını taşıyor. Parmak izi (kimlik + yazım + tarih) yeni yazımlar sayesinde ilk partiyle hiç çakışmıyor — 598 kişinin **290'ı** başka bir eğitimden zaten havuzda olduğu hâlde tek bir mükerrer kayıt oluşmadı. Canlıda doğrulandı: 2.930 satır, 2.930 benzersiz parmak izi.

✅ **Bir belge birden fazla firmaya önerilebilir — «öneri» satırdan ayrılıp kendi tablosuna taşındı** *(EGT-215, Tayfun'un talebi; kararlar Sets, 22.09.2026)*. Sorun şuydu: `legacy_training_records.customer_id` tek bir firma tutuyordu ve firma onayladığında satır kapanıyordu. Sets'in sorduğu senaryo bunu kırdı: *«bugün vitsanda çalışıyor, atamayı yaptık, sonra vitsandan ayrıldı, başka firmaya girdi, o firma kaydı girdiğinde ona da önereceğiz?»* — **evet**, çünkü sertifika kişinin, işverenin değil; yeni işverenin uyum takibi bizim elimizdeki tarihe bağlı. Artık her **firma-satır çifti** kendi durumunu taşıyor (`legacy_record_offers`: önerildi · kabul edildi · reddedildi) ve iki firma aynı belgenin kendi sertifika kaydını alabiliyor. Satırın kendi `state` kolonu duruyor ama artık **önerilerin özeti**; eski `customer_id`/`customer_user_id`/`certificate_record_id` kolonları yerinde bırakıldı, yazılmıyor, göçle önerilere taşındı.

⛔ **Aday gösterme reddedildi, firma kesişimi kabul edildi** — ayrım *iddianın yönü*. Sistemin *«bu satır muhtemelen Vitsan'ın»* demesi yasak (14.08.2026 kuralının aynısı; onaylı bir liste okunmaz, tıklanır — ve havuzda iş değiştirmiş 33 kişi var). Kabul edilen şey iki beyanın kesişimi: **operatör firmayı seçer**, **firma o kimlik numaralarını kendi sisteme girmiştir**. Sistem yeni bir şey iddia etmez.

**Üçüncü backoffice ekranı — «Firma eşleştirme»:** firma seç → o firmanın kayıtlı personelinin kimlik numaralarıyla havuz karşılaştırılır → firmada henüz bulunmayan kayıtlar listelenir → «tümünü firmaya öner». ⚠ *Tümü*, ekrandaki sayfa değil. ⛔ Firma kişiyi **önce kendi girmiş olmalı**; girmediyse kesişimde hiç çıkmaz — sıra değişmedi.

⛔ **Dosya başka firma diyorsa da gösterilir** *(Sets: «firma tckn ile kayıtlı dedikten sonra biz o bilgileri verebiliriz… ortada bir iddia vs yok»)*. Ölçüldü: Vitsan Gözetim'e önerilecek 49 satırın 7'si dosyada başka firma adı taşıyor (biri Alex Stewart) — bunlar iş değiştirmiş kişiler ve firma adına göre süzen yöntem onları ya yanlış firmaya götürür ya hiç bulamazdı. Ekran çelişkiyi **kolon olarak yazar**, karar operatörde.

⚠ **İki kimlik özeti karşılaştırılamaz:** havuz kök anahtarla, firma personeli firmanın kendi anahtarıyla özetlenir. Sorgu ucuz yönden kurulur — firmanın personeli kendi kapsamında çözülüp kök özeti hesaplanır ve havuz indeksli kolondan aranır (yüz çözme, üç bin değil). ⛔ Kimlik numarası backoffice'te **maskesiz** görünür *(Sets: «orası bizim mahrem bölgemiz ve üst düzey erişim bölgemiz»)*, ama okuma **erişim günlüğüne yazılır** — kaç personel çözüldü, kaç satır bulundu.

⛔ **Müşteri başka firmanın cevabını görmez.** Öneri tablosu «Vitsan onayladı» bilgisini taşır ve bu Vitsan'ın verisidir; müşteri paneli yalnız **kendi** önerisini ve havuz satırını okur (kırmızı çizgi 1).

✅ **Zaten kayıtlı kişi için müşteri ekranı kısaldı** — ve bu kozmetik değildi: kişi personel listesindeyken düğme «kişi ekle» sayfasına götürüyordu ve **aynı kişi için ikinci bir kayıt** açılabiliyordu. Artık «Belgeleri ekle» yandan bölme açıyor, yalnız sertifika tarihi ve numarası soruluyor, belgeler mevcut kayda ekleniyor. E-posta yalnız **kişi yaratılırken** zorunlu.

✅ **Ekran boşalmaz, yapılanı saklar — ve menü kalemi kalıcıdır** *(Sets, 22.09.2026, Tayfun'un EGT-215 dönüşü)*. Kural eskiden *«bekleyen yoksa kalemi gizle»*ydi (EGT-108) ve maliyeti aynı gün ölçüldü: Tayfun yetkisi olduğu hâlde ekranı bulamadı, çünkü Vitsan'ın iki şirketinde cevaplanacak bir şey kalmamıştı. Kalmamasının sebebi de görünmez bir başarıydı — **17:29'da 49, 17:30'da 15 kayıt kimseye sorulmadan sertifikaya döndü**, çünkü o kişiler zaten personel listesindeydi (15.08.2026 kuralı). İş yapılmıştı, firma hiçbir yerde göremiyordu.

⛔ **Yeni kural:** firmaya bir kez bile geçmiş veri önerildiyse kalem **kalıcı**. Hiç önerilmemiş firma yine görmez — *«sonsuza kadar boş duran kalem, insanlara onu görmezden gelmeyi öğretir»* kararı bozulmadı. Ekranda bekleyenlerin altında **«Daha önce yapılanlar»**: kabul edilenler ve geri gönderilenler, kimin ne zaman yaptığıyla.

⛔ **«Kimse sormadı» bir cümledir, boşluk değil.** `legacy_record_offers.confirmed_by` **NULL** = kişi zaten listedeydi, 3E bağladığında belge kendiliğinden eklendi; ekran bunu *«… tarihinde otomatik eklendi — bu kişi zaten personel listenizde olduğu için ayrıca sorulmadı»* diye yazar. Geçmiş kayıtlar işlem günlüğünden (`legacy.people_confirmed`) geriye doldurularak ayrıştırıldı. *Sets'in gerekçesi: «bugün tayfun var, yarın başka bir personel baktığında burada yapılan çalışmayı görebilmeli ve anlayabilmeli.»*

⚠ **Bugün ölçülen durum** *(22.09.2026, canlı)*: beş müşteri, **birden fazla müşterimizde kayıtlı tek kişi yok**. Vitsan Gözetim 97 kayıtlı personel / 49 önerilecek satır, Vitsan Mümessillik 28 / 15.

**Firma reddedebilir**, ve backoffice yetkilisi **önce müşteriden liste alarak** ilerler *(Sets, 14.08.2026)*. Ekran karşılığı: müşterinin gönderdiği listeyi yapıştırabileceği **toplu TCKN araması** — tek tek aramak kırk kişilik bir listede iş görmez.

⚠ **İkinci aşama: BI ve gösterge ekranı** *(Sets, 14.08.2026 — ilk adımda YAPILMAYACAK, ama ilk adım buna izin verecek şekilde kurulacak)*. Bu veri ticari olarak kıymetli: *"kişi kaç eğitim almış, biz kaç eğitim vermişiz, kaç firmaya eğitim vermişiz"*. Sonraki adımda konsolide bir ekran gelecek — bugüne kadar eşleştirdiğimiz, eşleştirme bekleyen, toplam verilen eğitim, eğitim verilen kişi — ve buradan **potansiyel müşteriler süzülüp satışçılara iletişim hedefi** verilecek.

⛔ Bunun ilk adıma yüklediği tek şart: **hiçbir satır tüketilip yok edilmez.** Excel satırı eşleştirildikten sonra da yerinde durur; üzerine yalnız **durum** yazılır (eşleşmemiş · taslak bağlı · doğrulanmış · reddedildi) ve **hangi kişiye/firmaya** bağlandığının izi düşer. Firma adı Excel'de nasıl yazıyorsa öyle korunur — «KARIŞIK» dahil — çünkü *"kaç firmaya eğitim vermişiz"* sorusu bir gün ona sorulacak; normalize edip atmak o cevabı yok eder.

---

### 4.2 Ofis — ortak omurga

**Ne yapar:** 3E personelinin paneli. Kimlik, yetki, grup ağacı, görevler, sistem günlüğü, profil/güvenlik.

**Durum:** omurga ✓ · kabuk ✓ · giriş akışları ✓ (şifre, 2FA, magic link, passkey) · görev sistemi ✓

| Karar | Kaynak |
|---|---|
| Tek grup ağacı; üyelik **seviye** taşır (`director`/`group_manager`/`staff`), rol taşımaz | AD-23 |
| Kapsam = üyeliklerden erişilen düğümler + altları | AD-23, `app/Support/Authorization/ScopeResolver.php` |
| İzin adları `resource.action.own\|group\|all`; seviye tavanı yalnız `.group`'u daraltır, `.all` bağımsızdır | `ofis-t4-plan.md` §Görev 2 |
| Kullanıcı oluşturulurken **parola belirlenmez** — davet maili ile kişi kendi belirler | `ofis-t4-plan.md` K-A |
| Yetki, kişi **ilk girişini yaptıktan sonra** tanımlanır; o ana kadar "yetki bekleyen kullanıcı" görevi düşer | `ofis-t4-plan.md` K-B, Sets 28.07.2026 |
| Kullanıcı silinmez, pasifleştirilir; dondurma ve kapatma gerekçesiz tamamlanamaz | AD-23, `ChangeOfficeAccountStatus.php` |
| Kapatılan hesap geri açılmaz — dönen kişi yeni hesap alır | `ChangeOfficeAccountStatus.php` |
| Son ofis yöneticisini kaldırmak **engellenmez** (backoffice her zaman üstte) | `ofis-onyuz-spec.md` P-5 |
| «Bu kayıtta başkası da çalışıyor» rozeti **üç ekranda**: teklif, eğitim, fatura detayı — hepsi tek kaydın sayfası. ⛔ Onay kuyruğuna **konmadı**: liste ekranıdır, satır başına bir kalp atışı yirmi satırda otuz saniyede yirmi istek ederdi. Yerine, karar verilirken kayıt bu arada sonuçlanmışsa **uyarı** çıkar | EGT-85, Sets 15.08.2026 |

**Veri:** `users`, `groups`, `group_memberships`, `permissions`, `roles`, `tasks`, `office_magic_links`, `office_passkeys`, `office_user_documents`, `office_user_customers`

**Ekranlar:** `resources/js/pages/Office/` — `Dashboard`, `Users` (Index/Show), `Tasks`, `SystemLog`, `Profile`, `Auth`, `Errors`

**Ticket:** EGT-54 (T1) ✓ · EGT-55 (T2) ✓ · EGT-59 (T4) ✓

---

### 4.3 Ofis — planlamacı

**Ne yapar:** eğitimi planlar — tür, tarih, yer, eğitmen, maliyet. Çatı §102: *"planlamacının oluşturmadığı hiçbir eğitim için satışçı işlem yapamaz."*

**Durum:** ✓ **tamamlandı** — EGT-63 (T1) ve EGT-64 (T2). Spec **v1.1 APPROVED** (`docs/specs/planlama-spec.md`, 43 kabul kriteri, heyet 165).

Ekranlar: liste, 5 adımlı sihirbaz, detay (eğitmenler, kabul edenler arasından seçim, plan düzenleme, maliyet, yedek liste, KUMS kilidi), aday listesi, tedarikçiler. Zamanlanmış işler: bakanlık bildirim görevi (günlük 07:00), teklif süresi (saatlik).

⚠ Takvim görünümü (`23`) **yazıldı** — `Office/Planning/Calendar.vue` (satır = eğitmen, sütun = gün; dört ton: eğitim, çakışma, müsait değil, bildirim son günü). 02.08.2026'da doğrulandı; kapsama matrisi çıkarılacak.

#### Gündem `3a`'ya büyür, ikinci ekran açılmaz *(Sets, 02.08.2026)*

⛔ **Kokpit diye ayrı bir ekran yok** — o ad 01.08.2026'da `Gündem` oldu (aşağıda). Tasarım `3a`'nın getirdikleri mevcut Gündem ekranının üstüne eklenir: KPI dörtlüsü (süresi dolan personel · 30 günde dolacak · bu hafta · ortalama doluluk), bölgesel talep (il bazında kartogram/matris), yaklaşan eğitimler tablosu (sekmeler: tümü / bildirim bekleyen / doluluk düşük), eğitmen müsaitlik şeridi. ⚠ İki ekran olsaydı bakanlık son günü ve eğitmensiz eğitim satırları ikisinde birden görünürdü; aynı işi iki yerde aramak gündemin varlık sebebine aykırı.

**Uygulandı** *(02.08.2026)* — `PlanningOutlook` + `Planning.vue`; kapsama matrisi `docs/reviews/tasarim-3a-kapsama.md` (36 satır, 10'u karar bekliyor). Müsaitlik şeridi takvimin **kendi sorgusunu** kullanır (`PlanningCalendar::between`), ikinci bir "kim müsait" okuyucusu yazılmadı. ⛔ Doluluk eşiği ekrandan sabit `0.5` olarak çıkarıldı, `training.low_occupancy_ratio` ayarı oldu (kırmızı çizgi 9 ihlali kapatıldı); eğitim statülerinin renk haritası da `Trainings/Index`'teki yerel `toneFor`'dan `StatusBadge`'e taşındı.

**`3a` matrisinin kapanan satırları** *(Sets, 02.08.2026)*: selamlama cümlesi olduğu gibi kalır, KPI kartı yeterlidir (3a-a) · **Bugün / Hafta / Ay** segmenti eklenir, ekran kişinin kendi gündemi olduğu için üç ufuk yeterlidir (3a-b) · global arama (⌘K) **her şey bittikten sonra ayrı bir tur** (3a-c) · eğitmensiz eğitim için ikinci uyarı şeridi açılmaz, tablodaki kırmızı yeterlidir — ⛔ **ama eğitmeni olmayan eğitim durdukça yöneticilere GÜNLÜK bildirim gitmeye devam eder** (3a-d) · trend rozeti yerine **"bu ay dolan: N"** yazılır, canlı türetilir, anlık görüntü tablosu tutulmaz (3a-e) · bölgesel talepte tek görünüm kalır (3a-f) · yaklaşan eğitimler listesinde kapsam yazılmaz, liste için ad+il yeterlidir; detay sayfasında kapsam zaten var (3a-g) · "Tümü →" **eğitimler listesine** gider ve o liste mevzuata/eğitime/eğitmene/lokasyona göre filtrelenip sıralanabilir olmalıdır (3a-h) · "Bugün ne yapmalıyım?" listesi **önce çıkarılır**, demo veri oturduktan sonra değerlendirilir (3a-i) · müsaitlik şeridi **tüm eğitmenleri** gösterir — ⛔ kısmi gösterim 3E'nin eğitmen işaret etmesi olurdu (3a-j).

**Uygulandı** *(02.08.2026)*: Gündem'e **Bugün / Hafta / Ay** segmenti (ufuk adreste: `/planlama?ufuk=`), KPI'da **"bu ay dolan: N"** (canlı türetiliyor, anlık görüntü tablosu yok), **"Bugün ne yapmalıyım?"** listesi (soldaki kartlardan türetiliyor — ikinci kaynak yok), müsaitlik şeridinde **tüm eğitmenler**, "Tümü →" **eğitimler listesine** gidiyor ve o liste artık **mevzuat / eğitim / eğitmen / lokasyon** filtreleri ve bu kolonlarda sıralama taşıyor. Eğitmensiz eğitim için `notifications:trainings-without-instructor` süpürgesi: ⛔ **her gün tekrarlar** — bu bir geri sayım değil, biri eğitmen atayana kadar yanlış kalan bir durumdur.

**`68` yazıldı, `67` dört sekmeye çıktı** *(02.08.2026)*. Tasarımcı `68`'i (yeni) ve `67`/`65`'i (revize) teslim etti; kapsama matrisi `docs/reviews/tasarim-65-67-68-kapsama.md`. Yazılanlar: **Eğitmen durumu** ekranı (dönem 3/6/12 ay + özel aralık, kategori ve il süzgeci, üç dikkat tonu — hiç eğitim yazılmamış · dönemde yok · **tek il baskın**, "başka sebep olabilir" paneli, dönem dağılımı çubukları, **CSV dışa aktarma + erişim logu**) · **Eğitimler sekmesi** (iptaller listede kalır, günü **null**; dönem özeti, bölge dağılımı, "neden az eğitim") · finansta **masraf drawer'ı** (tür süzgeci, limit, karar + gerekçesi, fiş önizlemesi), **yıl süzgeci ve arama**.

⛔ **Aday listesi üç gruba çıktı** *(tasarım `68`)*: Uygun · **Tercih etmiyor (amber, atanabilir, GEREKÇE SORAR)** · Uygun değil. Gerekçe teklife yazılır, kabul/seçimde atama kaydına kopyalanır — *"hep bu adama yazıyorlar" itirazının cevabı burada durur.*

⛔ **Bölge kısayolu kapsam dışı** *(Sets, 02.08.2026)*: il filtresi yeterli; coğrafi bölge veri seti dataint tarafında hazır, gerektiğinde eklenir.

**Eğitmen dağılımı: brief yazıldı, öneri motoru ERTELENDİ** *(Sets, 02.08.2026)*. `docs/design/egitmen-durumu-brief.md` — yeni "Eğitmen durumu" ekranı (dönem seçimli: son 3/6/12 ay + özel aralık; sütunlar: eğitim, gün, en çok gidilen üç il, son eğitim + üzerinden geçen gün, yaklaşan atama, belge durumu; varsayılan sıralama **en uzun süredir eğitim almayan üstte**; ⛔ para sütunu yok) ve mevcut eğitmen dosyasının eksikleri (Eğitimler sekmesi; finansta her hakediş/masrafın **doğduğu eğitime/programa tıklanabilir bağı**; program kırılımı).

⛔ **Öneri/atama motoru bu turda yazılmaz** — *"sistem önerdi, ben de seçtim" savunması bedava üretilmemeli*; yönetimle ayrıca konuşulacak.

**Eğitmende bölge tercihi eksik** *(Sets, 02.08.2026)* — ✅ **kapandı** *(21.10.2026'da doğrulandı: `instructor_travel_provinces`, aday listesinde «TERCİH ETMİYOR — TEKLİF EDİLEBİLİR» grubu)*. Eski metin: Şemada bugün yok: eğitmen **tüm Türkiye** mi, yoksa **seçili iller** mi kabul ediyor? Şikâyetin kökü bu — İstanbul'daki eğitmen sürekli İskenderun'a yazılıyor, Antalya'daki yalnız Akdeniz'e gidiyor. ⚠ Bir **tercih**tir, yasak değil: planlamacı yine teklif edebilir, ekran uyarır.

**Eğitmen dağılımı bir şikâyet konusu** *(Sets, 02.08.2026)*. İçeriden gelen geri bildirim: hep aynı bölgeye gönderilen eğitmenler (İstanbul'daki eğitmenin sürekli İskenderun'a yazılması), uzun süre hiç eğitim verilmeyen eğitmenler. **Dağılımı net gösteren bir ekran ve atama öneri sistemi** gerekiyor — ayrı iş kalemi.

**Sayılar canlı sorgudan gelir** *(Sets, 02.08.2026)*. Gecelik özet tablosu yok: rakam bir gün gecikirse "son 2 gün" uyarıları için ayrıca canlı sorgu gerekir ve ekran kendi içinde iki farklı gerçeklik gösterir. Yavaşlarsa özetleme o zaman eklenir.

#### Sorgulama (`20`) — bilgi verir, kimseyi eğitime yazmaz *(Sets, 02.08.2026)*

**Ne sorgular:** süresi dolmuş + eşik içinde dolacak + işe giriş kuralına giren personel; filtreler il, eğitim türü, firma, süre durumu. Sonuç **bilgi/yön vermek içindir**.

⛔ **Firma ile teklif sürecine girilmeden eğitim veya planlama doğmaz.** Bu yüzden seçimin iki çıkışı vardır, üçüncüsü yoktur:
1. **Satışa görev** — firmanın atanmış satışçısına *"müşteriyle iletişime geç, talep var mı netleştir"* görevi düşer. ⛔ "Teklif hazırla" DEĞİL: müşteri istemeden neyin teklifi verilecek?
2. **Açık eğitim taslağı** — il, eğitim türü ve tahmini kontenjan taşır. ⛔ **Kişi bağı kurulmaz**: ne katılımcı, ne öneri, ne firma listesi. Katılımcı ancak teklif onaylanınca müşteri tarafından girilir.

**Uygulandı** *(02.08.2026)* — `StaffCompliance` + `Office/Planning/Query.vue`; kapsama matrisi `docs/reviews/tasarim-20-kapsama.md` (42 satır, 7'si karar bekliyor). Satışa iletme `TaskKind::ComplianceOutreach` görevini firmanın **atanmış satışçısına** açar; açık eğitim taslağı planlama ekranını `?il=&egitim=&kisi=` ile açar (`from_demand` — kişi/firma taşımaz).

**`20` matrisinin kapanan satırları** *(Sets, 02.08.2026)*: sonuç **yetki ekseninde** şekillenir — İstanbul'daki satışçı İzmit'i göremiyorsa İzmit'in süresi dolanlarını da göremez; yönetici hepsini görür (20-a) · dışa aktarma ~~**şimdilik yok**, gerekirse sonradan~~ ✅ yazıldı, erişim günlüğüyle (`StaffQueryController::export`, 21.10.2026'da doğrulandı) (20-b) · "＋ Koşul" yerine iki hazır filtre daha: **lokasyon** ve **işe giriş tarihi** — dinamik koşul kurucusu yazılmaz (20-c) · sonuç **firma bazında gruplanabilir** (20-d) · "planlama kuyruğu" diye bir şey yoktur, teklif zaten planlanmış eğitime yazılır; onay sonrası iş müşterinindir (20-e, yukarıda) · onaylı teklifin katılımcıları mevcut eğitime bağlanabilir (20-f, yukarıda) · **bölgeye duyuru müşteri tarafına bırakıldı**: müşteri panelinde "planlanmış açık eğitimler" listesi olacak, tek tıkla teklif istenebilecek, bildirim de oradan gider (20-g).

**Sorgulama ikinci turda genişledi** *(02.08.2026)*: **lokasyon** ve **işe giriş tarihi aralığı** filtreleri eklendi (dinamik koşul kurucusu yazılmadı), sonuç **firma bazında katlanabiliyor** (sayfayı değil, tüm sonucu katlar).

⛔ **Birim kişi değil, (KİŞİ, EĞİTİM) çiftidir** *(EGT-178, 18.08.2026)*. Ekran kişiyi tek bir sertifikayla — **en uzak tarihlisiyle** — yargılıyordu; beş eğitimi olan biri 2031'e kadar geçerli tek bir belgesinin arkasında görünmez oluyordu. Canlıda ölçüldü: sertifikası olan **42 kişinin 14'ü**, dolmuş ya da eşik içinde dolacak belgesi olmasına rağmen listede yoktu — devralınan bir firmanın personelinin **tamamı** bu yüzden kayıptı. Karar *(Sets, 18.08.2026: "karar verebilmek için tüm bilgiye ihtiyaçları var")*: **satır yine kişidir**, ama «İhtiyaç» sütunu kişinin **bütün** eğitimlerini rozet olarak taşır (kırmızı dolmuş · amber yaklaşıyor · **gri geçerli**); satırın tarihi, durumu ve sıralaması **en acele** olanı gösterir. Eğitim filtresi kişinin herhangi bir **ihtiyacına** bakar — geçerli belge ihtiyaç değildir; «en yoğun ihtiyaç» sayıları bütün ihtiyaçları sayar; dışa aktarmada «Tüm eğitimler» sütunu vardır.

**Erişim logu: sorgu + sonuç kimlik listesi** *(Sets, 02.08.2026)*. Tek satır: kim, ne zaman, hangi koşullarla sorguladı, kaç sonuç döndü ve **hangi kayıt no'ları** döndü. ⛔ Ad, kimlik numarası, firma loga yazılmaz — yalnız işaretçi. Sebep: "o anda hangi verilere erişti" sorusu bire bir cevaplanmalı (AD-21), ama append-only audit'in içinde ikinci bir kişisel veri kopyası birikmemeli (AD-06 + KVKK silme hakkı). ⚠ `anon` eklentisi buna çözüm değildir — o **dump** anonimleştirmesidir (AD-16), çalışma anı için pseudonym katmanı yazılmamıştır.

#### Kaynağı olan kararlar

| Karar | Kaynak |
|---|---|
| Statü omurgası: Taslak → Planlandı → [bakanlık adımı] → Devam Ediyor → Tamamlandı/İptal | `HANDOFF.md` §1 |
| Bakanlık adımı **iki modeldir**: bildirim (3 gün önce, beklenmez) / onay (15 gün önce, onaysız eğitim geçersiz) | AD-15, `mevzuat-kurallari.md` §10 |
| ⛔ **KUMS kimliği her iki modelde de eğitimi «Bakanlığa bildirildi» yapar; İdare onayı ayrı ve ZORUNLU DEĞİL bir kayıttır** *(Sets, 20.08.2026 — EGT-181, önceki kuralın gevşetilmesi)*. Onay modelinde durum eskiden bildirimle hiç ilerlemiyordu; doğru kuraldı ama ekranı yoktu — onay kaydını girecek alan hiçbir sayfada çizilmemişti, `POST .../bakanlik/onay` caller'sız duruyordu. Canlıda ölçüldü: «Söküm sürveyi» eğitimine 18–20.08 arası altı kez kimlik girildi, altısında da eğitim «Planlandı» kaldı (detay, satış kokpiti, planlama listesi). Yeni yapı: kimlik → `ministry_notified`; onay yazısının referansı eğitim detayındaki ayrı kutuya girilince → `ministry_approved`; onaysız başlatma **engellenmez**, DG-2020 md.14/8 AD-27 usulüyle **uyarı** olarak yazılır. Geç gelen onay statüyü geri çekmez. Sıkışmış kayıtlar tek seferlik migration ile `ministry_notified`'a çekildi, hiçbiri onaylı sayılmadı | EGT-181, `NotifyMinistry`, `RecordMinistryApproval` |
| Planlama kendi kodunu taşır (`PL-2026-XXXX`); onaylanınca **son ek korunarak** `EG-` olur; eski kod ölmez, yönlendirir | AD-19 |
| Kontenjan göstergesi üç sayı; yedek liste sıralı ama **otomatik terfi yok** | `HANDOFF.md` §15, çatı §15 |
| Eğitmen ücreti override'ının yeri **planlamadır**, satış değil *(revizyon 28.07.2026)* | çatı §185, `egitmen-spec.md` §4.4 |
| Maliyet kalemi iki tutar taşır: planlanan + gerçekleşen; sıfırdan farklı sapma gerekçesiz kaydedilemez | `egitmen-spec.md` §7.2–7.3 |
| Planlama onayı: global ayar + planlamacı bazında ayar; daraltma doğrudan, genişletme onaya | `egitmen-spec.md` §7.4 |
| Eğitmen ataması: ihtiyaç → N adaya teklif → kabul → kesinleşmiş atama. Tek eğitmen bu modelin `N=1` hâli | `egitmen-spec.md` §6.1 |
| Kontenjanı `first_accepted` modunda kabul, `selection` modunda seçim kapatır | `egitmen-spec.md` §6.2 |
| Çakışmanın tek kaynağı **kesinleşmiş atamalardır** | `egitmen-spec.md` §6.5, K-4 |
| Gece vardiyası kuralı uyarı seviyesindedir; katılımcı vardiya verisi toplanmaz | çatı §218, YÖN-2019 md.14/1 |

#### Planlama turu kararları *(Sets, 29.07.2026)*

Hepsi `docs/specs/planlama-spec.md` v1.1'e (heyet 165 APPROVED) yazıldı; gerekçeleri orada.

| Karar | § |
|---|---|
| Eğitim "kapalı/açık" görünürlük taşır; kapalıysa bir veya N firma listelenir — üç eğitim türü için tek mekanizma | §2 |
| Kapalı eğitimde firmayı **planlamacı** atar. ⚠ `HANDOFF.md` §5'ten bilinçli sapma | §2.1 |
| Geri çekme (`planned`→`draft`) yalnız **hiçbir teklif kaydı açılmamışsa**; alınmış onayı düşürür | §4.3 |
| Teklif açılmışsa tek yol iptal + yeniden planlama | §4.3 |
| İptal onaya tabidir; ayrı yetki, kapsamı AD-23'ten | §11.1 |
| Kontenjan tavanı **serttir** — tanımdaki azami aşılamaz, aşağı çekilebilir | §5.1 |
| Dört sayaç: kapasite · kesinleşen · teklif bekleyen · isim girilmiş | §5 |
| Bekleyen teklif bloklamaz, onay bloklar; kilit veritabanında | §5, §5.3 |
| Kontenjan yetmezse müşteri onayı kaydedilir, tahsis "kontenjan teyidi" kuyruğuna düşer | §5.4 |
| Yedek liste **talep** bazlı (firma + kişi sayısı); otomatik terfi yok, seçim gerekçesiyle audit'e | §6 |
| Eğitim öncesi iptal ≠ no-show — ikincisi terfi tetiklemez | §6.1 |
| Asgari katılımcı uyarıdır, engellemez (mevzuatta asgari sayı yok) | §5.5 |
| Yetkinlik: eğitmen × verebileceği eğitim, şart × N belge türünden biri | §9.1, §9.2 |
| Şartı eksik eğitmen aday listesinde görünmez (K-1 korunur) | §9.3 |
| Donmuş kanıt seti: belge **dosyaları** eğitime kopyalanır. ⚠ KVKK bedeli kayıtlı | §9.4 |
| Yapılış biçimi şimdilik yalnız bilgi alanı | §1.4 |
| Çakışma yalnız eğitmene bakar; mekân kısıtı yok | §8.2 |
| Talep kendi kaydıdır; teklife dair hiçbir alan taşımaz | §7 |
| Bakanlık bildirimi ayrı yetkidir | §10.2 |
| Bildirilmiş eğitimin iptalinde bildirim **görevi** açılır, kapanmadan iptal tamamlanmaz | §11.2 |
| Tedarikçiler yönetilen kayıt + bölgesel fiyat tarihçesi | §12 |
| İzin vokabüleri `training.*`; `egitmen-spec` §7.4'ün `planning.approve` adı düzeltildi | §13 |
| Eğitim, verildiği mevzuat ve müfredat sürümünü saklar | §1.2 |
| Gece vardiyası ve TMGD koordinasyonu uyarıdır, kısıt değil | §8.1 |
| Kapalı eğitimi atanmış satışçılar + kapsamı yeten yöneticiler görür | §2.3 |
| Çoklu kurumsal eğitimde her firma yalnız kendini görür | §2.3 |
| Taslak onaysız `abandoned` yapılır; `completed` hiç iptal edilemez | §4.4, §4 |
| Bildirim son tarihi **görev** üretir, geçerse yükselir | §10.3 |
| Kanıt seti kopyaları orijinalle aynı erişim rejiminde; eğitimi görmek yetmez | §9.6 |
| Saklama tabanı eğitim tanımının `record_retention_years` alanından | §9.5 |
| Mevzuatın tavan koymadığı eğitimde 3E'nin genel tavanı (aşılabilir, onaya gider) | §5.1 |

**Veri:** ✓ `trainings`, `training_customers`, `training_requests`, `training_reserve_entries`, `training_ministry_notifications`, `training_instructor_requests/_offers/_assignments`, `training_cost_items`, `training_evidence_documents`, `suppliers`, `supplier_price_quotes`, `instructor_training_definition`, `instructor_requirement_document_type`

✓ `training_participants.training_id` FK'sı **kapatıldı** (`2026_08_14_000700`) — chunk 5'in açık bıraktığı kalem.

**Planlanan izinler:** `training.view/create/update/approve/cancel/cancel_approve/assign_instructor/notify_ministry` (hepsi AD-23 kapsamlı) · `training_request.view` · `supplier.view/create/update`

**Ekranlar:** ✓ `Office/Trainings/{Index,Create,Show}.vue` — liste (üç sayılı kontenjan göstergesi), 5 adımlı sihirbaz, detay (KUMS kilidi, yedek liste). T2'de: takvim + tedarikçiler (`23`)

**Ticket:** EGT-25 (spec ✓ APPROVED) · **EGT-63** (T1 planı — eğitim kaydı, kontenjan, bildirim, iptal) · **EGT-64** (T2 planı — eğitmen ataması, maliyet, tedarikçiler)

---

### 4.4 Ofis — satışçı

**CRM aktivitesi ayrı kayıt değil, görevin türüdür** *(Sets, 02.08.2026)*. `30` kokpiti ve `33` takvimi "planlanmış arama", "ziyaret", "teklif takibi" kalemlerini tek akışta gösterir; bunlar **göreve eklenen tür** olarak yazılır (arama / ziyaret / takip / serbest). ⛔ Ayrı bir aktivite tablosu açılmaz: görev zaten teklife ve müşteriye bağlanabiliyor, ikinci bir zaman akışı doğsaydı kullanıcı her kayıtta "bu görev mi aktivite mi" diye düşünmek zorunda kalırdı.

**Ne yapar:** planlanmış eğitim üzerinden teklif hazırlar, müşteri ilişkisini yürütür.

**Durum:** ✓ **teklif dikeyi yazıldı** (EGT-74 chunk 4, 01.08.2026): teklif listesi + detay (`31`), kalem/indirim/limit aşımı, yönetici onayı, müşteriye gönderim, revizyon, kapatma, fatura ve muhasebe bildirimi. Talep tarafı chunk 3'te (`34`). CRM yüzeyi de yazıldı: e-posta gönderme penceresi, görüşme notları, teklife bağlı görevler, zaman akışı draweri. `30 Satisci Dashboard`, `32 Musteri Karti`, `33 Takvim` yok.

⛔ **Kim satışçıdır: liste daraltılmaz, ayırt edilir** *(Sets, 14.08.2026 — EGT-134, Tayfun'un bulgusu)*. Müşteri kartındaki «Sorumlu satışçılar» kutusu aktif olan herkesi listeliyordu; eğitmen de oradaydı. Ölçüt **teklif yazma yetkisi** oldu — `AssignableSalespeople`, `OfficeSellers`'ın zaten kullandığı tanım. ⚠ Ama **yöneticiler çıkarılmadı**: *"yönetici olanlar düşemez çünkü onlar da satışçı yetkisine sahip; buradaki çözüm net bir şekilde kimin ne olduğunu göstermek."* Her isim rolüyle (en üst yerleşimi) rozetlenir ve role göre süzülür. ⛔ Genel kural: **bir listenin okunmaması "fazla kişi var" demek değildir** — çıkarmadan önce ayırt edilebilir mi diye sorulur.

⛔ **Potansiyel müşteri ayrı bir yapıdır, `customers`'ta bir durum değildir** *(Sets, 19.10.2026 — EGT-216, Tayfun'un bulgusu)*. İhtiyaç kısmi bir CRM: firma aranır, görüşme notu tutulur, firma dönerse yetkili tanımlanıp müşteri olarak devam edilir. Bugün yetkili eklemeden firma açılamıyor, firma olmadan da not tutulamıyor.

- **Ayrı tablo, ayrı sayfa.** *"customers tablosunda exceptionlar tanımlamaya başlarsak bir sürü güvenlik kontrolü ve yapısal değişikliği bypass edecek hamleler yapmak zorunda kalırız."* Ölçüldü: `customers` kiracının kendisidir (açılışta yetki satırları kurulur, satır güvenliği kendi kimliğine bağlıdır, portal hesabı buradan doğar) ve `app/` içinde onu okuyan 33 dosyada 46 sorgu var — durum alanıyla her birine "potansiyelleri hariç tut" yazılırdı. Ayrı tablo potansiyel bir firmanın teklif/eğitim akışına girmesini **yapısal olarak** imkânsız kılar. ⚠ Ek kazanç (Sets): iki taraf ayrı tablolarla ilerleyebildiği için birine eklenecek şey ötekini ilgilendirmez.
- ⛔ **Ayna tablo değil.** Potansiyel müşteri yalnız arama sürecinin alanlarını taşır; müşteriye dönüşürken eksik zorunlu alanlar ve yetkili dönüşüm formunda tamamlanır. Gerekçe: ayna tablo, `customers`'a yapılan her şema değişikliğinin ikinci kez yapılması demektir; yapılmadığı gün dönüşüm sessizce veri kaybeder.
- ⛔ **Dönüşümde satır silinmez**, «müşteriye dönüştü» durumuna geçer ve oluşan müşteriyi gösterir. Sets: *"ileride raporlarda potansiyel müşteri aramaları, potansiyelden müşteriye dönenler, dönenlere satışlar gibi metrikleri ölçebiliriz; tarihçe olarak da tutma şansımız olur."*
- **Notlar ve görevler kopyalanmaz, yeniden bağlanır.** `record_notes` ve `tasks` zaten çok biçimli (`subject_type`/`subject_id`); dönüşüm aynı transaction'da ve audit'le konuyu müşteriye çevirir.
- **Görünürlük kendi kapsamıyla kurulur.** `office_user_customers` kiracı üzerinden çalışır, potansiyel müşteri kiracı değildir. Açan kişi otomatik sahibidir; şube kapsamıyla (aşağıdaki ticket) çakışmayacak biçimde kurulur.
- ~~**Mükerrer kontrolü iki tabloya da bakar** (vergi numarası) — zaten müşterimiz olan firma potansiyel olarak yeniden açılmasın.~~ *(19.10.2026 — engelleme olarak okunmuştu, aşağıdaki satırla değişti)*
- ⛔ **Vergi numarası eşleşmesi UYARIDIR, engel değildir** *(Sets, 19.10.2026)*. Gerekçe: *"Vitsan, müşteri olarak ödemeyi yapan tek bir çatı firma var ama altta 3 tane firması var; bu yüzden vergi numarası tarafında çok katı yaklaşmadık."* Ölçüldü: `customers.tax_number`'da bugün de tekillik yok. Eşleşme müşterilerde ve açık potansiyellerde aranır; form kaydetmeden önce uyarır, kullanıcı «yine de kaydet» derse açılır ve bu onay audit'e yazılır. **Uyarı firmanın adını yazar** — okuyanın kapsamı dışında olsa bile: *"firma adı gizli bilgi değil, müşteri ekleme ekranını açabilen kişiler adı görebilir; böylece içeride konuşma şansları olur."* ⚠ Yalnız ad; firmanın başka hiçbir bilgisi ya da sayfası kapsam dışına açılmaz.
- ⛔ **Temas telefonu açık gösterilir, sayfanın açılışı erişim loguna yazılır** *(Sets, 19.10.2026 — heyet 809 F-6)*. `phone` `audit.sensitive_fields`'tedir ve kırmızı çizgi 13 hassas okumanın loglanmasını ister; satışçının her aramada «göster»e tıklaması istenmedi. ⛔ **Kiracısız kaydın audit'i açık bir beyanla yazılır, örtük blokla değil** *(Sets, 20.10.2026)*: *"null yazılabilir hâle getirmek zafiyet oluşturur; yazması gereken yerde yazmazsa kaçırırız. Yeni parametreli bir yapı daha doğru."* Üç yazıcı (`AuditWriter`, `AccessLogger`, model audit'i) bugün kiracıyı bulamayınca okuyanın kapsamına düşüyor; tek müşteriye atanmış kişide o müşteriye yazıyorlar. Çözüm: `officeRecord: true` parametresi ve `OfficeRecord::belongsToNoCustomer()` sözleşmesi; beyan doğrulanır, kiracılı kayda verilirse hata verir. `TenantContext::forOffice()` bu iş için **kullanılmaz** (plan K-25).
⚠ Aynı kural **müşteri kartına da uygulanır**: firma kişilerinin telefonu bugün logsuz gösteriliyordu (`CustomerPeople.php:88`) — Sets: *"bu işte düzelt"*.
- **Kayıtta yapılabilenler:** görüşme notu, görev, planlı görüşme (arama/ziyaret/çevrimiçi). Teklif, fırsat ve eğitim ataması kapalı. ⚠ Sets'in *"toplantı takvimi"* önerisinin karşılığı zaten vardı — `Takvimim` (`33`, `SalesCalendar`) `sales_meetings`'ten okuyor; yeni ekran açılmaz, görüşme potansiyel müşteriye bağlanabilir ve Takvimim ile Satış kokpitine düşer.
- ⛔ **Dönüşümde yetkili ELLE doldurulur**, temas kişilerinden önerilmez. Sets: *"satış amaçlı görüştüğümüz kişi ile firma yetkilisi aynı kişi olmak zorunda değil … kazara tıklayarak birine sisteme giriş yetkisi vermek gibi hataların da önüne geçer."* Temas kişileri potansiyel kaydında **salt okunur tarihçe** olarak kalır, hiçbiri firma kişisi/hesabı olmaz; müşteri kartı o geçmişe bağlantı verir.
- ⚠ **Satış menüsü 6 üst kalem** *(Sets, 20.10.2026)*: `HANDOFF.md` kural 12 grup başına ≤5 diyor; potansiyel müşterilerle Satış grubu 6'ya çıktı. Hiçbir kalem alt kaleme alınmadı — *"6'ya çıksın, tamamladığın zaman bakar görerek değerlendiririz."* `ReachableFeatureTest`'te adlandırılmış istisna (`sales => 6`). ~~Değerlendirme yapılınca kaldırılır.~~ ⛔ **Kalıcı** *(Sets, 20.10.2026, ekranı canlıda gördükten sonra)*: *"6 tane olması bir sıkıntı yaratmamış, böyle kalabilir."* Satış grubu için tavan 6; diğer gruplar için kural 12'nin 5'i geçerli.
- **Ekran:** yeni tasarım istenmez. Liste `02` deseni, sayfa `32`'nin alt kümesi; çizilmemiş dört öğe (menü kalemi, «Müşteriye dönüştür» formu, «Dönüştü» rozeti, mükerrer uyarısı) mevcut parçalarla kurulur. Menüde **Satış** grubunda durur.
- Kişisel veri tarafı ayrıca ele alınmaz: *"o konu zaten çözüldü"* (Sets, 19.10.2026).
- Spec turu yok, doğrudan plan *(Sets: "tablo yapıları net, proje zaten çalışıyor")*; plan heyete gider.
- **Durum (20.10.2026):** plan `docs/plans/potansiyel-musteri-plan.md` v1.3 — heyet 809 ✗ (6) → 810 ✗ (1) → 811 ✓ → v1.3 (uygulama başında bulunan audit kiracı genişlemesi) → 812 ✓. Kod yazıldı: `potential_customers`, `potential_customer_contacts`, `sales_meetings` ekleri, `app.customers_with_tax_number()`; `PotentialCustomerPolicy`, `PotentialCustomersInReach`; `/satis/potansiyel-musteriler` (liste + sayfa + dönüşüm); Satış menüsü; müşteri kartında «Potansiyel müşteri geçmişi»; kiracısız audit beyanı (`OfficeRecord`, `officeRecord: true`). Yardım `satis-potansiyel-musteri.json`. ⚠ Yan düzeltme: firma kişisi telefonu artık erişim logunda (`CustomerPeople`, K-23). Tarayıcı spec'i `tests/Browser/potential-customers.spec.js` (yerel veriye yalnız okur; iki örnek kayıt Sets onayıyla 20.10.2026'da bir kez yazıldı). TeamCity #1819 yeşil (2624 test), production'a 20.10.2026'da yayımlandı (`20261020-023247`); canlıda yalnız Tayfun'un hesabında potansiyel müşteri yetkileri var (audit 15933). EGT-216 → In Review (Tayfun). Açılan ticket'lar: EGT-219 (şubeler), EGT-220 (kiracısız erişim logu, mevcut dört okuma), EGT-221 (tasarım `32`'de eksik dört öğe).
- ⛔ **EGT-220 kararları** *(Sets, 20.10.2026)*: (1) Eğitmen kimlik numarası açma, eğitmen belgesi okuma ve kurum belgesi okuma 3E'nin kendi kaydıdır — erişim logu `officeRecord: true` ile **kiracısız** yazılır. (2) **Yoklama defterinin okuması, defterde katılımcısı olan her müşterinin izine birer satır** yazılır (defter birden fazla firmanın personelini taşıyabilir). (3) Eğitmen dosyasındaki belge önizlemesi 03.08.2026'dan (EGT-74) beri tanımsız bir `view` yetki kuralı soruyordu ve herkese 403 dönüyordu; **dosyayı görebilen açar** (`instructor.view` + şube erişimi), sağlık raporu gibi belgeler için ayrı kural konmadı. **Durum:** `8c9845c0`, TeamCity #1826 yeşil (2730), prod `20261020-202238`; canlıda bu dört türden yanlış etiketli satır yoktu (yalnız okuma). EGT-220 → In Review (Tayfun).
- ⛔ **EGT-224 kararları — sorgu kayıtlarının kiracısı** *(Sets, 20.10.2026)*: (1) Personel uyum sorgusu ve dışa aktarımı, **sonuçta personeli çıkan her firmanın izine** o firmanın kendi kayıt numaralarıyla birer satır yazar; sonuç boşsa tek müşterisiz satır. (2) Muhasebedeki **müşteri cari dökümü o müşterinin izine** yazılır. (3) Eğitmen iş yükü, sistem logu ve mali tablo dışa aktarımı ile backoffice geçmiş kayıt sorguları 3E'nin kendi kaydıdır, müşterisiz. (4) Beyansız sorgu kaydı reddedilir (EGT-216'daki «açık beyan» ilkesi). (5) Canlıda 22–24.08.2026'da 18 numaralı müşterinin izine yanlış yazılmış **4 boş sorgu kaydı olduğu gibi kalır** (append-only). **Durum:** `ca7d817f`, TeamCity #1827 yeşil (2737), prod `20261020-204454`; ekran yüzü yok → EGT-224 Fixed. EGT-223 (eğitim geliri kuruş sapması, `9407dbc5`) aynı yayınla → In Review (Tayfun).
- ⛔ **EGT-221 kararı** *(Sets, 20.10.2026)*: dört eksik öğeyle birlikte **müşteri kartının ızgarası da tasarım `32`'ye hizalanır** (üst bant 1 : 1 : 1,2; gövde esnek kolon + 480px), potansiyel müşteri sayfasıyla aynı `grid-cols-card-band` / `grid-cols-card-body`. Ayrıca: «tüm işler» yalnız bize atanan ticket'ları kapsar (EGT-221, 217, 218, 44, 41); Tayfun'a atananlar onda kalır. **EGT-221 ek kararları** *(Sets, 20.10.2026)*: (a) **«Birincil» seçilebilir bir işarettir**, firma başına en fazla bir kişi — potansiyel müşteri kişilerinde ve müşterinin panel yetkililerinde; dönüşümde taşınmaz (kişiler taşınmıyor). (b) **Görevi açan ve atanan kişi düzenler** (başlık ve son tarih), değişiklik denetim kaydına yazılır. (c) Başkasına atanmış görevin tamamlama dairesi **pasiftir** ve satır kime atandığını yazar; «yalnız atanan kapatır» kuralı değişmez. (d) Potansiyel müşteride aktivite seçici **Tümü / Not / Görüşme**, «… kayıttan son N'i · Tüm aktivite →» listeyi aynı sayfada açar; müşteri kartında tasarımdaki gibi Tümü / Not / E-posta / Teklif ve mevcut tüm-aktivite sayfası. **Durum:** `59b74027`, TeamCity #1829 yeşil (2743), prod `20261020-210959`; kapsama `docs/reviews/tasarim-32-kapsama.md` (EGT-221 bölümü; eski açık satır 32-f «tüm eğitimler» bağlantısı ~~hâlâ açık~~ → **kapandı, 21.10.2026**). **32-f kararı** *(Sets, 21.10.2026)*: «N eğitimin tamamı →» **firmaya özel sayfayı** açar (`/musteriler/{firma}/egitimler`, «Tüm aktivite» sayfası gibi), genel eğitim listesine müşteri süzgeci eklenmez — sayfa her eğitimde firmanın kendi katılımcı sayısını yazar, kartla aynı sayıyı. Bağlantı yalnız kartın gösterdiğinden fazla eğitim varsa çıkar.

- ⛔ **21.10.2026 durum analizi kararları** *(Sets, `docs/reviews/durum-2026-10-21.md`)*: (a) analizdeki dört grubun **hepsi** düzeltilir — kırıklar ve bugünkü işler, yetki açıkları, yardım metinleri taraması, kayıtların temizliği — ve başka eksik bulunursa o da; **her bulgu grubu ayrı ticket**, ekranda test edilebilenler test prosedürüyle Tayfun'a. (b) **Eğitim detayı listeyle aynı kapsamla süzülür**: `training.view.own/.group` taşıyan merkez okuyucu kapsamı dışındaki eğitimi detayda da göremez ve değiştiremez; `.own/.group` ekleri güncelleme, iptal, eğitmen atama ve Bakanlık bildirimi yetkilerinde de çözülür (planlama-spec P-24). Eski «merkez okuyucunun detayı değişmesin (P-7)» yorumu bu kararla **geçersiz**. **Uygulama (EGT-235):** tek süzgeç `TrainingsInScope::apply` — liste, detay ve detaya bağlantı veren bütün ekranlar (takvim, gündemler, kuyruk, müşteri kartı) aynı cevabı verir; «yalnız şube okuyucusunu daralt» ayrımı (`applyToBranchReaders`) kaldırıldı, yoksa takvim detayı açılmayan eğitim gösterirdi. Yazma yetkileri kendi kapsamıyla sorulur: `update.own` taşıyan kişi `view.all` ile gördüğü her eğitimi düzenleyemez. (c) **Doğrulamada «süresi doldu» dördüncü durumdur** *(Sets, 21.10.2026)*: geçerlilik tarihi geçmiş, iptal ya da düzeltme görmemiş belge yeşil «geçerlidir» değil, amber «süresi doldu» olarak gösterilir (plan B-8'in üç durumu genişler). (d) **«Bugün» Türkiye saatiyle** *(Sets, 21.10.2026)*: belge tarihi, belge serisinin yılı ve fatura numarasının yılı `Europe/Istanbul` takvimine göre hesaplanır; saklanan zaman damgaları UTC kalır. (e) **Portalda teklifler bir izne bağlıdır** *(Sets, 21.10.2026)*: menü ve sayfa «Teklif talep etme» ya da «Teklif onaylama» yetkisi olana açılır; yeni izin açılmaz. (f) **Şüpheli yardım görselleri** açılıp kontrol edilir, yanlışlar 1440px'te yeniden çekilir. (g) **Eksik özellikler turu** *(Sets, 21.10.2026)*: (1) süper admin her yere girer — **eğitmen paneli dahil** («bunu defalarca söyledim»; §514'teki kural uygulanmamıştı); (2) bildirimi kapatılmış e-posta adresi yeniden açılabilir; (3) aktif kuralı olmayan **24 bildirim olayının hepsi doğal alıcısına açılır** — varsayılan kanal uygulama içi, e-posta kişinin tercihi, backoffice kural ekranından kapatılabilir (13.08.2026'daki «aktif edilmedikçe gitmez» durumu bu kararla **değişti**); (4) Bakanlık bildiriminde doğum tarihi, ofisten doğrudan katılımcı ekleme, firmanın imzalı sözleşmesini indirmesi, eğitim detayından pakete dönüş, teklif listesinde ad bağlantısı ve **içerik paketinin video/ses tipi — kendi depomuzda, yetkiyle, her oynatma erişim günlüğünde** yapılır; ⏸ **SMS sağlayıcısı bekler** (Logfiks'te kullanılan sağlayıcı büyük olasılıkla, erişim henüz açılmadı), ⏸ **e-Fatura entegratörü bekler** (belli değil), ⏸ **Zoom/YouTube (EGT-41) bekler** (3E hesapları kurulmadı); (5) planlama takviminde boş güne tıklama/sürükleme, eğitim günü sayacı, yoklama başlığında KUMS rozeti ve «N salonda» sayacı yapılır; (6) il bazında tedarikçi maliyet geçmişi kontrol edilip ekrana bağlanır. EGT-221 → In Review (Tayfun). (h) **Yükleme sınırı: belgeye 50 MB, video/ses kaydına 2 GB** *(Sets, 21.10.2026)*. Ölçüm: canlıda PHP 2 MB / 8 MB idi, 8 MB'ı aşan her form 413 alıyordu; Livewire 12 MB'ta kesiyordu (Filament 50 MB vaat ediyordu). Sınır **paylaşımlı php.ini'de değil**, bu sitenin `public/.user.ini`'sinde ve depoda (EGT-256); nginx ile eşitliği `tests/Arch/UploadLimitTest.php` kilitler. 2 GB yalnız kaydın kendi yükleme ucunda açılır (video/ses ticket'ı). (i) **İçerik paketinde video/ses kaydı** *(Sets, 21.10.2026)*: (1) kayıt **özel diskte şifresiz** durur — paket belgelerinin şifreli deposu dosyanın tamamını belleğe açıyor, oynatıcı ise parça (Range) istiyor; web'den doğrudan erişilemez, yalnız yetki soran uçtan sunulur, her oynatma erişim günlüğüne yazılır; (2) backoffice'ten **parçalı yükleme** (32 MB'lık parçalar, ilerleme çubuğu; sunucu sınırı 64 MB'ta kalır, «2 GB» dosya boyutu sınırıdır); (3) müşteri kaydı **paketin belge listesinde** «İzle/Dinle» ile tarayıcının kendi oynatıcısında açar, indirme düğmesi yoktur (kararlı birinin kaydetmesi engellenemez — bilinerek kabul); (4) yalnız **MP4 ve MP3/M4A**, sunucuda dönüştürme yok. (j) **İçerik paketi ofisten elle de tanımlanır** *(Sets, 21.10.2026)*: **hem müşteri sayfasından hem paket kataloğunun «Kim lisansladı» kartından**; yalnız başlangıç tarihi seçilir, **bitiş paketin erişim süresinden gelir** ve pencerede gösterilir (ilk yazılan «süre seçilir» ifadesi aynı gün düzeltildi — teklif yolunun kuralıyla aynı); yetki `content_package.grant`, işlem denetim kaydına yazılır; onaylı teklif yolu da devam eder. (k) **Deploy betiği nginx'i kendisi yükler** *(Sets, 21.10.2026)*: önce `nginx -t`, geçerse yeniden yükleme; hata varsa deploy durur. Bunun için `www-data`'ya `/etc/sudoers.d/3eegitim`'de `nginx -t` ve `systemctl reload nginx` izni verildi (Sets onayı; www-data site ayar dosyasının zaten sahibi). (l) **E-postayla giden tek kullanımlık bağlantı, sayfa açılınca değil kişinin basışıyla harcanır** *(Sets, 21.10.2026 — EGT-262)*. Ölçüm: Cotecna'nın e-posta güvenliği (Proofpoint) davet bağlantısını teslimden 48 sn sonra bir bulut adresinden açtı, kişi «geçerli değil» gördü. Firma daveti düzeltildi (EGT-262); **katılımcı giriş bağlantısı da aynı biçimde düzeltilir** (açılınca «Devam et» düğmesi, bağlantı basınca harcanır — ayrı ticket). (m) **Yetkisiz kişiye giden davet ayrı metin taşır** *(Sets, 21.10.2026)*: firmanın panel yetkisi olmayan kişisine giden davet «erişiminiz açıldı, personelinizin takibini yürüteceksiniz» demez, yetkileri firma yöneticisinin vereceğini söyler; yönetici davetinin metni aynı kalır. İkinci kişinin yetkisiz eklenmesi kararı (11.08.2026, EGT-93) **değişmedi**.

⛔ **Şubeler: tek merkez 3E Group, diğerleri şube gibi çalışır** *(Sets, 19.10.2026)*. İzmit'te bağımsız bir ekip oradaki satışları üstlenecek; İzmir'deki şirket için ayrı arayüz ya da tam erişim verilmeyecek, 3E Eğitim içinde **ayrı grup** olarak konumlanacak. Şube yalnız kendisine verilen işleri yapar. Ölçülen bugünkü durum: grup ağacı ve müşteri ataması hazır; eğitim kapsamı yalnız talepten planlanan eğitimde yazılıyor (kapsamsız eğitim herkesin); eğitmenlerde kapsam yok, `training.assign_instructor.{group,all}` tanımlı ama okuyan kod yok. Ticket: EGT-219.
  - ⛔ **Şube bir yetenek setiyle kurulur** *(Sets, 20.10.2026)*: *"branch'ler 3 işi de yapabilsin yetenek olarak, ama atıyorum İzmit'e sadece satışı verdi, İzmir'e satış ve planlamayı verdi, Antalya'ya satış, planlama ve eğitmeni verdi gibi bir şey yapabilelim. Branch kurulurken neleri yapabileceği seçilsin."* Üç yetenek: **satış · planlama · eğitmen**. Sabit bir şube tipi yoktur; her şube kurulurken seçilir.
  - **Herkese açık eğitimlerin şubeye görünmesi seçilebilir** *(Sets, 20.10.2026)*: *"görünüp görünmeyeceği seçilebilsin."* **Eğitim başına** seçilir: merkez açık eğitimi açarken hangi şubelerin göreceğini seçer.
  - **Yapı: şube kaydı + dikey düğümler** *(Sets, 20.10.2026)*. «İzmit» ayrı bir şube kaydıdır; seçilen her yetenek için ilgili dikeyin altında düğüm açılır (Satış → İzmit, Planlama → İzmit). AD-23'ün dikey-önce ağacı ve kapsam çözümleyicisi değişmez. ⚠ Menüler şubeye göre değişmez — menü kalemleri yetkiden çizilir; daralan, kalemlerin **içindeki kayıtlardır** (Sets'in sorusu üzerine netleşti).
  - ⛔ **Yetenek bir yetki tavanıdır** *(Sets, 20.10.2026)*: şubede planlama kapalıysa planlama düğümü açılmaz, şube üyelerine planlama yetkisi **verilemez** (yetki ekranı o kutuları kapalı gösterir) ve yanlışlıkla verilmiş yetki de çalışmaz.
  - **Şube backoffice'te kurulur**, yetenekleri orada seçilir *(Sets, 20.10.2026)* — grup ağacının bugün yönetildiği yer.
  - **Eğitmen:** eğitmen yeteneği olmayan şubenin planlamacısı **yalnız merkezin o şubeye açtığı eğitmenleri** atar *(Sets, 20.10.2026)*.
  - **Şubenin planladığı eğitimi kimin göreceği planlarken seçilir** *(Sets, 20.10.2026)*.
  - **Mali:** *"şube eğer açılırsa kendi mali tablolarını görebilir ama bu yetki verilmezse göremez. Merkez tarafında da şube bazlı kırılımı görebilmemiz gerekir raporlarda."* *(Sets, 20.10.2026)* — şube kapsamlı mali görüntüleme + merkez raporlarında şube kırılımı kapsama girdi.
  - **Spec:** `docs/specs/sube-spec.md` v0.2 — kalan 14 karar orada, her biri Sets'in cümlesiyle: yetenek × yetki tablosu (fatura/kurum belgesi/şablon yetkiyle şubeye verilebilir), yetenek kaldırma ve şube kapatma = uyarı → atama soruları → taşı, kapalı ve açık eğitimde şubeler planlarken seçilir, kapsamsız eğitim yalnız merkeze, Takvimim/kokpit potansiyel müşteri yetkisiyle de açılır, şube dışına genişletme Planlama Direktörü onayına, şube eğitmenini merkez kapsamı yetiyorsa atar, eğitmen şubelere eğitmen dosyasında açılır, gelir faturada seçilen şubeye yazılır, fatura kapsamlı, **fatura kesen şirket şubeye göre değişir** (kendi unvanı, vergi no, antet, kendi seri; teklif de onunla).
  - ~~fatura kesen şirket şubenin özelliğidir~~ → ⛔ **3E şirketleri ayrı bir kavramdır** *(Sets, 20.10.2026)*: *"3E Group altında 3E Danışmanlık, STTMGD ve 3E Akademi var… 'biz bu 3 firmadan birisi üzerinden fatura kesebilmek istiyoruz' da diyebilirlerdi … olması gereken oluyor."* Ayrı şirket ayrı seri *("olmak zorunda")*; şube kurulurken şirketi seçilir; faturayı kesen kişi yetkisi varsa başka şirket seçer; kesimde belgeye sabitlenir.
  - ⚠ **Ölçüldü (20.10.2026):** fatura numarası diye bir şey yoktu — `FT-2026-7K4M` rastgele bir referans kodu (R-2), sistemde fatura **belgesi** de yok. ⛔ *(Sets)* *"Şimdi sıralı numara + belgeyi yapalım, belge için brief ver ben tasarımcıya ileteyim."* Numara = 3 harf şirket öneki + yıl + 9 hane; banka hesapları ve yasal ibare şirkette; şahıs TCKN belgede açık. Brief: `docs/design/fatura-belgesi-brief.md`.
  - **Genişletme onayı yetkiyle** *(Sets, 20.10.2026, plan P-8)*: «Planlama Direktörü onaylar» = `training.widen_visibility` yetkisini isteği kapsayan kapsamla taşıyan kişi; Planlama Direktörü `all` ile taşır, başkasına da verilebilir (AD-23: direktör için özel `if` yok).
  - **Gider şubesi eğitimde seçilir** *(Sets, 20.10.2026)*: *"planlamayı merkez yaptı, İzmir'in eğitmenleriyle verdi, faturayı siz kesin dedi — kimin olacak? Seçebilmeleri gerekir."*
  - **Yetenek tavanı düğüm başına; aday eğitmenler atama yetkisiyle görünür** *(Sets, 20.10.2026, heyet 815 sonrası)*.
  - **Süreç: spec → plan, ikisi de heyete** *(Sets, 20.10.2026)*; ardından kod ve implementation review.
  - **Şube yöneticisi kendi şubesine kullanıcı açar — yetkisi varsa** *(Sets, 20.10.2026)*: *"eğer kullanıcı açma ve yetki düzenleme yetkisi varsa yapabilsin kendi şubesine, bu yetkileri yoksa yapamasın."* AD-23'ün kendi kuralı: yetki kapsamdan değil açık izinden gelir; kapsam şubeyle sınırlı.
  - ⛔ **Şube kullanıcısının eğitim görünürlüğü** *(Sets, 20.10.2026, uygulama sonrası soru)*: «Merkezde herkes» kapsamı şubeleri **kapsamaz** — şubenin görmesi için şube ayrıca seçilir. Ama şube kullanıcısına **`training.view.all`** verilirse kapsam seçimi onu **sınırlamaz**: her eğitimi (kapsamsız eskiler dahil) görür. `group`/`own` kapsamlı şube kullanıcısı yalnız kendisine seçilenleri görür.
  - ⛔ **Fatura belgesi (tasarım `39`) için kararlar** *(Sets, 20.10.2026)*: (1) Alıcının fatura bilgileri **lokasyondan seçilmez**; müşteri eklenirken/düzenlenirken bir **«Fatura bilgileri»** bölümü olur (vergi bilgileri, adres vb.) — *"doğru şey bu"*. (2) Kalem açıklamasının ikinci satırı (tarih · yer · fiyat türü) gerekiyorsa eklenir; kesimde kopyalanır. (3) KDV oranı backoffice'te **eğitim tanımı ve içerik paketi başına** tanımlanır (boşsa genel oran); teklif kalemi oranı oradan alır, faturaya kalem bazında geçer, toplamlarda oran başına KDV satırı. (3b) Çok oranlı teklifte **teklif çıktısı (37) ve portal teklifi de oran başına KDV satırı** gösterir; tek oranlıda bugünkü tek satır kalır *(Sets, 20.10.2026, heyet 824 F-R4 üzerine)*. (4) İndirim belgeye **bizim tarafımızdan**, teklif çıktısındaki (`37`) gibi toplamlarda tek «İndirim» satırıyla eklenir. (5) Yasal ibare: şirket kaydındaki alan, şimdilik boş kalabilir; müşteri isterse değişiklik ister. Ticket **EGT-222**; süreç: kısa plan (`docs/plans/fatura-belgesi-plan.md`) + heyet, sonra kod ve implementation review *(Sets, 20.10.2026)*.
  - **Durum — fatura belgesi (20.10.2026):** plan v1.1 (heyet 824 ✗ → 825 ✓). Kod yazıldı: migration `2026_11_02_000100` (müşteri fatura alanları, tanım/paket/teklif kalemi `vat_rate`, `invoice_items.detail`, şifreli `invoices.recipient_snapshot`), `QuoteTotals` oran başına KDV, `IssueInvoice` kalem oranı + ikinci satır + alıcı kopyası (`InvoiceRecipient`), sorgu `InvoiceDocument`, rotalar ofis `/muhasebe/faturalar/{invoice}/belge|antet` ve portal `/faturalar/{token}/belge|antet`, ortak sayfa `organisms/InvoiceDocument.vue` + `resources/css/invoice.css`, «Yazdır / PDF» düğmeleri, fatura formunda «fatura bilgileri eksik» uyarısı. ⚠ Portal rotası plandaki «model binding» değil, portalın gerçek kalıbı olan **opak token** (plan F-11'in örnek verdiği `/teklifler/{quote}/yazdir` de token'lı). Implementation review 826 ✗ (4 bulgu) → 827 ✓; TeamCity #1825 SUCCESS (2725 test); prod release `20261020-191300`; `In Review` + Tayfun, 13 adımlı test prosedürü (20.10.2026).
  - ⛔ **Fatura belgesi — heyet 826 F-R4 cevapları** *(Sets, 20.10.2026)*: (a) banka kutusunun altındaki «Alıcı: faturayı kesen şirket · üçüncü hesap için yer ayrılmıştır» tasarımcı notudur, **basılmaz**; (b) baskıda yasal ibare içerikten sonra tek sefer akar, sayfa dibine sabitlenmez; (c) «İPTAL» damgası **tasarımdaki gibi**: son sayfada yer yoksa basılmaz, yalnız üst iptal kutusu ve ayaktaki «İPTAL» kalır; (d) teklif çıktısının şahıs müşteride T.C. kimlik no'yu erişim günlüğüne yazmaması **bu ticket'ta (EGT-222) düzeltilir**. Çok sayfalı belge (F-R4 a) 40 kalemle PDF'te doğrulandı.
  - **Durum (20.10.2026):** spec v0.6 (heyet 819 ✓), plan v1.2 (heyet 822 ✓). Kod A1–A7 yazıldı ve `main`'e itildi (`52e64d67`…): `branches` + `groups.branch_id`, `BranchCeiling` (izin + kapsam), backoffice «Şubeler» (aç / yetenek ekle / yetenek kaldır / kapat — iki adımlı soru akışı) ve «3E şirketleri»; eğitim kapsamına `branch` türü, `TrainingsInScope` şube okuyucusu, `training_visibility_requests` + onay türü «Görünürlük»; `instructor_branch_access` + aday havuzu; `SalesWorkspacePolicy`; `issuing_companies` (varsayılan şirket `company.*` ayarlarından), `invoice_number_sequences`, fatura `invoice_number`/`issuer_snapshot`/`revenue_branch_id`, `invoice.view.{own,group,all}`, `issuer.choose`; `trainings.cost_branch_id`, mali tablo şube kümesi + merkez kırılımı. Lokal tam koşu yeşil (2320 + omurga-rls 378). Yardım: `planlama-egitim-planlama`, `operasyon-onay-kuyrugu`, `planlama-egitmen-atama`, `satis-potansiyel-musteri`, `satis-teklif-hazirlama`; changelog `2026-10-20`. ⚠ Bekleyen: implementation review, TeamCity, deploy; varsayılan şirketin vergi no + seri öneki girilmeden canlıda fatura kesilemez (rollout); yasal ibare metni Sets'te; fatura belgesi tasarım bekliyor.

✅ ~~**Bekleyen kapanış adresi:** `ReserveSeats` ve `AddToReserveList` (koltuk rezervasyonu ve yedek listeye kayıt) hâlâ çağıransız~~ **Kapandı** *(21.10.2026'da doğrulandı)*: `ReserveSeats` → `ApproveQuote`, `AddToReserveList` → `TrainingController`. Eski metin: — teklif kalemi kendi tahsisini `quote_items.allocation_status` üzerinde tutuyor, katılımcı kaydı ayrı bir akış. Kapanış: teklif onayından katılımcı kaydına geçen tur.

| Karar | Kaynak |
|---|---|
| Satışçı yalnız **planlanmış** eğitimi satar; planlama yapılmadan teklif süreci başlamaz | çatı §106 |
| Satışçı maliyeti **görür, değiştiremez**; yalnız müşteriye indirim tanımlar | çatı §185, `egitmen-spec.md` K-23 |
| Satışçı planlama yazışmasını hiç görmez | `egitmen-spec.md` §8.2, K-23 |
| İndirim limiti aşımı engellenmez, **onaya gider** | çatı §181, `fiyatlama-spec.md` §4.2 |
| İki fiyat türü: eğitim/gün fiyatı ve kişi başı fiyat | çatı §180 |
| Teklif onayı yalnız panelden; maildeki "onaylıyorum" onay değildir | AD-14, AD-20 |
| Onay anında teklif metni sürümü + hash + zaman damgası + kullanıcı kaydedilir | çatı §179 |
| Satışçılar gruplanır ve müşteri bazlı yetkilendirilir | çatı §190, `office_user_customers` |
| **«Satışçı» diye bir işaret yok — ölçüt teklif yazma yetkisidir** | Sets, 14.08.2026 (EGT-134) |
| Açık eğitimde katılımcı sahipliği satışçı bazındadır; tam liste planlamacı ve yöneticilerde | `HANDOFF.md` §15 |
| Teklif yazışması `tk-2026-xxxx@teklif.3egrp.com` inbound adresiyle teklife eşleşir | AD-20 |

**Veri:** `quotes`, `quote_items`, `quote_versions`, `quote_approvals`, `invoices`, `invoice_items`, `invoice_notifications` ✓ · `user_discount_limits` onay kolonlarıyla ✓ · `prices`, `price_lists`, `price_groups`, `price_bands` (fiyatlama turu) ✓

**Ekranlar:** `31` ✓ (liste + detay) · `30`, `32`, `33` yok

**Ticket:** EGT-74 (chunk 4) · EGT-26 (satış spec'i, açılmadı — kalan CRM yüzeyi orada)

**Kapandı:** `EGT-58` — `DiscountCeiling` artık `sellerMax ?? globalMax` ile çalışıyor (`min()` değil) ve **iki yarısı birden** yazıldı: limit tanımı onaya düşer, aşan teklif onaya düşer.

---

### 4.5 Ofis — operasyon ve direktörler

**Ne yapar:** onay kuyrukları, kullanıcı yönetimi, muhasebe, analitik. Ayrı kokpit almaz — rol bazlı menüyle planlamacı + satışçı kokpitlerini görür.

**Durum:** kullanıcı yönetimi ✓ · sistem günlüğü ✓ · onay kuyrukları ✓ (`/onaylar`, `/onaylar/gecmis`) · muhasebe ✓ (`/yonetim`, faturalar, tahsilat) · anket sonuçları ✓ · belge şablonları ✓

✅ ~~**Bekleyen kapanış adresi:** `OpenTrainingDocumentTasks` (eğitimin kendi evrak dosyası — yoklama, sınav, malzeme) yazıldı, çağıranı yok.~~ **Kapandı** *(21.10.2026'da doğrulandı)*: `DeliverTraining` çağırıyor. Kapanış: operasyon turu, eğitim başlatma akışına bağlanır.

| Karar | Kaynak |
|---|---|
| Operasyon/üst yönetici ayrı kokpit almaz | `HANDOFF.md` §Rol→dashboard |
| Limit aşımı akışı kırmaz: onay kuyruğuna düşer, ret gerekçelidir, audit'lenir | `HANDOFF.md` §4 |
| Masraf onayı iki ayarla verilir: "kendi masrafını onaylayabilir" / "bağlı eğitmenlerinkini onaylayabilir" | `egitmen-spec.md` §7.8 |
| Kimse kendi masrafını onaylayamaz; `approve_group` kapsamı AD-23'ten gelir | `egitmen-spec.md` K-20 |
| Kişi bazlı kullanım izleme yasak; analitik yalnız agregat, kişi eşiği admin-tanımlı (varsayılan 2) | AD-21, kırmızı çizgi 17 |
| Müşteri kullanıcısının "son aktif" damgası ofiste görünür — adlandırılmış tek istisna | `omurga-spec.md` §7.5 L-1a |

**Ekranlar:** `61 Yonetici Tanimlar`, `62 Yonetici Muhasebe`, `63 Yonetici Kullanici Log` (kullanıcılar · kullanıcı kokpiti · sistem günlüğü · Onaylar yapıldı; muhasebe yapılmadı)

**Müşteri portalı — "Açık Eğitimler" sayfası tasarımcıya gitti (03.08.2026)**

Brief: `docs/design/acik-egitimler-brief.md`. Sebep: tasarım `11` ve `15`'in menüsünde madde çizili, sayfa yok; satışçıdaki "düşük doluluk" sinyali ve açık eğitim duyurusu **inecek zemin bulamıyor**; paket vitrini (`35`) tek başına duruyor.

| Karar (soru değil) | Neden |
|---|---|
| **Fiyat yazmaz** | Fiyat firmadan firmaya değişir; liste fiyatı pazarlığı bozar ve "benimki neden pahalı" sorusunu doğurur. Paket vitrininde verilen kararın aynısı — müşteri iki farklı dil görmemeli |
| **Doluluk yüzdesi değil, KALAN kontenjan** | "%80 dolu" baskı cümlesidir, "4 kişilik yer var" bilgidir |
| **Doğrudan kayıt yok**, tek çıkış "Kontenjan İste" | Yer parayla alınır; teklif ve onay zinciri atlanamaz. Katılımcı listesi hakkı onaylı teklif sonrası doğar |
| Eğitmen atanmamışsa **boş bırakılır** | "Atanmadı" yazmak müşteriye 3E'nin iç işini anlatmaktır |
| "Personelimin ihtiyacı olanlar" süzgecinde **kişi adı yok** | Süzer, listelemez; kim olduğu sorusunun yeri personel ekranı (AD-21) |

**Tasarımcıya sorulan dördü:** sekme mi/blok mu · dolmuş eğitim listede kalsın mı · paket vitrini taşınsın mı · ihtiyaç süzgeci varsayılan açık mı.

**`17 Musteri Acik Egitimler` teslim edildi ve yazıldı (03.08.2026)** — matris: `docs/reviews/tasarim-17-kapsama.md`

Tasarımcının dört cevabı ve uygulanışı:

| Cevap | Uygulama |
|---|---|
| **Alt alta blok**, sekme değil — paketler eğitimlerin altında, mor kenarlıkla ayrışır | `Portal/OpenTrainings.vue`: kart ızgarası → fiyat açıklaması → paket şeridi (`role-*` mor token) |
| **Dolmuş eğitim listede kalır** (soluk, "YER KALMADI"), eylemi **"Yeni Tarih İste"** olur | Aynı eğitim tekrar açılabilir; listeden düşürmek "böyle bir eğitim yok" dedirtir |
| **Şerit + kendi sayfası** — vitrin taşınmaz, buraya 3 kart + "Tüm paketler (N) →" | Menüdeki "İçerik Paketleri" maddesi `35`'e gitmeye devam eder |
| **İhtiyaç süzgeci varsayılan AÇIK**, açık olduğu ve nasıl kapatılacağı **yazıyla** durur | `?tumu=1` kapatır; boş hâlde süzgeci kaldırma bağlantıları aynı ekranda |

Yeni kararlar (bu turda alındı):

| Karar | Neden |
|---|---|
| İhtiyaç penceresi **firmanın kendi `compliance_warning_days` ayarı** (varsayılan 3E ayarı) | Uyum kartları da onu kullanıyor; farklı pencere "ihtiyacınız yok" derken panonun "9 kişi doluyor" demesi olurdu (kırmızı çizgi 9) |
| **Kalan yerden fazlasını istemek serbest** — engel değil, uyarı satırı | "12 lazım, 3 yeriniz var" 3E'nin bir sonraki eğitimi planladığı cümledir; hiçbir şey rezerve edilmediği için korunacak bir şey yok |
| Teklif isteği hangi açık eğitim için yapıldığını taşır: **`training_requests.wanted_training_id`** | Mevcut `training_id` "bu talepten planlanan eğitim" demek; ikisi tek kolona sığmaz |
| Kart çapası `id="egitim-{id}"` | Duyuru bildirimi ilgili karta insin diye (duyurunun kendisi #78) |
| İhtiyaç sayısı yalnız **belgesi olanlardan** doğar | Hiç belgesi olmayan kişi `ComplianceSummary`'de "bekliyor" durumunda ve hangi eğitimi beklediğini söyleyen kayıt yok — tahmin, personel ekranının açıklayamayacağı bir sayı üretirdi |

**Rol bazlı giriş — kim ne görüyor, nereye düşüyor (03.08.2026'da kapatıldı)**

⛔ Kök adres (`/`) **boş bir sayfa** çiziyordu ve dört gündemin ikisi hiçbir menüde yoktu. Artık:

| Kim | Nereye düşer | Rayda gördükleri |
|---|---|---|
| **Eğitmen** (kaydı olan) | `/egitmen` — Gündemim | Gündemim · Eğitimlerim · Takvimim · Müsaitliğim · Masraflarım · Cari durumum · Belgelerim |
| **Satışçı** (`quote` policy) | `/satis` — Satış kokpiti | Satış kokpiti · Takvimim · Teklifler (+ yetkisi varsa faturalar) |
⛔ **Devretme kuralı DEĞİŞENE uygulanır** *(EGT-177, 18.08.2026)*. "Kimse kendi ulaşamadığını devredemez" kuralı, satışçının **dokunulmamış** eski atamalarına da uygulanıyordu; ekran her kayıtta bütün listeyi gönderdiği için, sonradan **pasifleştirilmiş** bir firmayı hâlâ taşıyan iki satışçı hiçbir firmaya atanamaz olmuştu (canlıda ölçüldü — aynı firma iki kez kaydedilmiş, kopyası pasifleştirilmişti; ekran çıplak 403 veriyordu). Kural artık yalnız **eklenen** firma için işler. ⚠ Sessiz yarısı da kapandı: aktörün **göremediği** bir atama, o listeyi gönderen ekran onu çizemediği için silinmiş sayılmaz — görünmeyen atamalar korunur.

| **Planlamacı** (`training` policy) | `/planlama` — Planlama gündemi | Planlama gündemi · Planlama takvimi · Sorgulama · Eğitimler · Eğitmenler · Eğitmen durumu · Tedarikçiler |
| **Operasyon direktörü** (onay yetkileri) | `/yonetim` — Operasyon gündemi | Hepsi + Onaylar · Anket sonuçları · Muhasebe · Müşteriler · Kullanıcılar · Satışçılar · Sistem günlüğü |
| **Yeni hesap** (hiçbir yetki yok) | Kendini anlatan sayfa | Gündem · Görevlerim · Bildirimlerim |

⚠ Birden fazla şapka takan kişi hepsini rayda görür; bu sıra yalnız **nereye indiğini** belirler — en dar rol kazanır. Kanıt: `tests/Feature/Omurga/RoleLandingTest.php`.

⛔ **Menü hatası bulundu ve düzeltildi (03.08.2026):** `AppSidebar` kendi key→adres tablosunu tutuyordu ve **11 madde** o tabloda yoktu (sorgulama, planlama takvimi, satış kokpiti/takvimi, teklifler, faturalar, muhasebe, satışçılar, onaylar, bildirimler, eğitmen durumu) — hepsi `'/'`'e, yani Gündem'e düşüyordu. Adres artık maddeyle birlikte route'tan geliyor; menünün ikinci bir adres kaynağı yok. **Genel durum çıkarıldı ve aynı gün kapatıldı: `docs/reviews/durum-2026-08-03.md`** — katılımcı paneli (`50`), portal güvenlik sayfası (`10`'un müşteri yarısı), `03 Grafikler` kataloğu, ekranı olmayan 12 ayar ve rol bazlı iniş. (kullanılmayan tasarımlar, ekranı olmayan ayarlar, yazılmamış ekranlar).

Uyum kartındaki eylem (`11`) tasarım `17`'nin dediği gibi bu sayfaya bağlandı: **"İhtiyacı olanlara açık eğitim var mı? →"**.

⛔ **Düzeltme (Sets, 03.08.2026):** süzgeç şeridine tasarımcının verdiği date-range yerine iki standart `DatePicker` konmuştu — geri alındı, `DateRangePicker :inline="false"` kullanılıyor. Aynı turda 375px kart sıralaması tasarımdaki gibi yazıldı (kalan yer + tarih ilk iki bilgi) ve `ConfirmModal`'a **`wide`** varyantı eklendi (520px'lik modaller için). Kural: **handoff'ta bir kontrol çizilmişse o kullanılır.**

**Açık eğitim duyurusu + müşteri bildirim tercihleri (#78) — 03.08.2026'da yazıldı**

| Karar | Neden |
|---|---|
| Duyurunun **kendi izni**: `training.announce` | Tek firmaya teklif yazmak ile kırk firmaya toplu mesaj atmak farklı iki eylem; ikincisi daha az elde durmalı |
| Üç hedef kitle: **ihtiyacı olanlar** / bölge / seçtiklerim | İlki tasarım `30` sinyalinin kastettiği kitle; diğer ikisi satışçının veriden bilmediği şeyi bildiği hâller (aksi hâlde kendi mail programına gider, hiçbir şey kayda geçmez) |
| **Önizleme gönderimden ayrı adres** | Kime gittiğini ancak göndererek öğrenen kişi iki kez gönderir |
| Firma başına **N gün** sınırı (varsayılan 14, admin-tanımlı) | Sınır bize konur, müşteriye değil: dört satışçı kendi eğitimini duyurunca aynı koordinatöre haftada dört mail gider |
| Atlanan firma **gerekçesiyle kaydedilir** | 40 firmaya duyurup 9'una ulaşan satışçı sebebini görmeli |
| Duyuru **kapatılabilir** (`training.open_announced` zorunlu değil) + `/portal/bildirimler` | Müşterinin kapatabileceği ilk mesaj bu; anahtar mail dipnotunda değil, kendi panelinde |
| Bildirim yalnız **teklif isteyebilen** kişiye gider | Hesabı yalnız kendi sertifikasını okumak için olan kişiye ticari mesaj gitmez |

⛔ **KARAR (Sets, 03.08.2026): duyuru yalnız UYGULAMA İÇİ.** E-posta gönderilmez. Gerekçe: *"in-app bildirimde bir sorumluluk yok"* — müşterinin kendi açtığı paneldeki bir satır yükümlülük doğurmaz, aynı cümlenin posta kutusuna gitmesi 6563/İYS kapsamında ticari elektronik iletidir. Böylece rıza sorusu **cevaplanmıyor, sorulmuyor**. `NotificationEvent::channels()` bu olay için `['in_app']` döner; posta istenirse o listeye bir kelime eklemek yeterli — ve İYS işi onunla birlikte gelir.

Panodaki `open_trainings_nearby` **null olmaktan çıktı**: aynı okuyucudan besleniyor, dolmuş eğitimler şeritten düşürülüyor.

**`30` + `33` satışçı kokpiti ve takvimi (02.08.2026)** — matris: `docs/reviews/tasarim-30-33-kapsama.md`

| Karar | Neden |
|---|---|
| **Görüşme ayrı bir kayıt** (`sales_meetings`): arama · ziyaret · çevrimiçi, saatli, kişili | Görev terminli iştir, görüşme randevudur. Takvimde buluşurlar, veride ayrı dururlar; randevuyu görev tablosuna katlamak her göreve olmayan bir saat taşıtırdı |
| Görüşmeyi kapatmak **müşteri kartına not** yazar | Not kayıttır (düzeltilemez, audit'li); görüşme satırı yalnız plandır. Aynı satır hem plan hem kayıt olsaydı randevuyu düzenleyerek geçmiş yeniden yazılabilirdi |
| İptal **gerekçe ister**, not yazmaz | Konuşulmadıysa kaydedilecek konuşma yoktur |
| **Aylık hedef veri oldu** (`seller_targets`): kişi + ay + tutar + gerekçe, yönetici koyar, kimse kendi hedefini koyamaz | Üç ekran (`30`, `62`, `63`) hedefi gösteremiyordu çünkü ortada veri yoktu. Kural "hedef olmasın" değil, "iş kuralı admin verisidir" idi (AD-08, kırmızı çizgi 9). **Hedef yoksa yüzde yok — sıfır sayılmaz** |
| Takvim **beş kaynağı** birleştirir: görüşme · görev · teklif geçerlilik son günü · müşterinin eğitim günü · fırsatın beklenen kapanışı | Tasarım `33`'ün kendi cümlesi: kaynağı ne olursa olsun her iş burada buluşur. Türler ayrı tonda kalır — son gün randevu değildir |
| **Hafta sonu ayrımı YOK** *(Sets, 03.08.2026)* | Lojistik 7/24 yaşayan bir sektör: liman hafta sonu çalışır, eğitim pazar günü koşar. Hafta sonunu gizlemek ya da soluklaştırmak, sektörün normal haftasını sisteme "anormal" diye yazmaktır. Planlama takvimindeki soluk hafta sonu da kaldırıldı; saat listesi 00:00–23:30 |
| `DateTimePicker` atomu yazıldı | Native `datetime-local` yasak (kırmızı çizgi 17); eksik atom yazılır — gün `DatePicker`'dan, saat yarım saatlik listeden |
| Satışçı **eğitim yaratmaz** | Sinyal şeridinin çıkışı planlama talebi; satılabilir stok planlamacının açtığıdır |

**`62` muhasebe (02.08.2026)** — matris: `docs/reviews/tasarim-62-kapsama.md`

| Karar | Neden |
|---|---|
| **Gelir = kesilen fatura**, satılan teklif değil | Onaylı teklif bir söz, fatura bir taleptir; marj sözlere göre hesaplanırsa her onayda oynar. Satışçının kendi kitabı (onaylı teklifler) aynı sayfadaki satışçı tablosunda |
| **Maliyet = hakediş + ONAYLI masraf + eğitim maliyet kalemleri** (fiili varsa fiili) | Onaylanmamış masraf henüz maliyet değil — reddedilebilir; sayılsaydı marj reddedildiğinde oynardı |
| Müşteri alacağı ile eğitmen borcu **netleştirilmez** | İki ayrı yön: firmanın geç ödemesi eğitmene borcu azaltmaz. Tasarım da iki ayrı tablo çiziyor |
| Eğitmen borcu **cari formülünün aynısı** (kazanılan + onaylı masraf − onaylı ödeme) | İkinci bir formül, yöneticinin toplamının altındaki satırlarla çelişmesi demek |
| **Hedef sütunu yok** (satışçı tablosunda) | `63` kokpitindeki kararla aynı: hedef tanımlı veri değil, uydurma paydayla yüzde yazılmaz (kırmızı çizgi 9) |
| Mali tablo **`financial.view`** ile, fatura yetkisiyle değil; üstelik MFA kapılı | Evin marjını okumak, evrak kesmekten başka bir iştir |
| Cari **tek tarihli hareket sütunu**; silme yok, ters kayıt | Bakiyeye nasıl gelindiği sorusunun cevabı sütunun kendisi (AD-11) |
| **e-fatura entegratörü yazılmadı** | Tasarımın kendi bandı "entegratör bekleniyor — manuel gönderim" diyor. Doğrulanmamış 3rd-party alan adı/statüsü koda girmez (kırmızı çizgi) |

**Fırsat (CRM boru hattı) — yeni kavram (Sets, 02.08.2026)**

*Sets: "bu tarafta ne kadar BI tool gibi davranırsak, ne kadar CRM temelli ve veri dayanaklı yaparsak o kadar kullanışlı olur."*

| Karar | Neden |
|---|---|
| **`opportunities` tablosu**: müşteri · sahip · konu · kaynak · aşama · tahmini tutar · beklenen kapanış · teklif bağı · kapanış gerekçesi | Yalnız notta yaşayan konuşma sayılamaz, takip edilemez, karşılaştırılamaz. "Boru hattında ne var" sorusu sayıyla cevaplanmalı |
| Aşamalar: yeni · görüşüldü · nitelendi · teklif verildi · kazanıldı · kaybedildi | Konuşmanın gerçekten geçtiği yollar |
| **"Kazanıldı" elle yazılmaz** — müşterinin onayladığı bir tekliften gelir | Aksi hâlde boru hattı satışçının umutları listesi olur, mali tablo da onu devralır |
| **Kayıp her zaman gerekçeli** (DB CHECK) | Fiyat · ertelendi · başka firmaya gitti — bir boru hattını iyileştiren tek veri |
| Tahmini tutar **boş bırakılabilir** ve boş olanlar sayılır ama toplanmaz | "Henüz bilmiyoruz" gerçek bir durum; sıfır yazmak boru hattını olduğundan küçük gösterir |
| Kaynakta **"uyum sinyali"** var | "O ekran gerçekten iş üretiyor mu" sorusu ancak böyle cevaplanabilir |
| Fırsat müşteri kartında açılır/ilerletilir; mali tabloda satışçı satırında **potansiyel** olur | Konuşma firmayla olur; toplamı yönetici okur |
| **Günlük KPI snapshot'ına gelir · maliyet · vadesi geçen da yazılıyor** | Mali tablodaki yön oku ancak yazılı geçmişten çıkar; iptal edilen fatura geçmişi geriye dönük değiştirir |

**`3a` gündem — kalan kararlar (02.08.2026)**

| Karar | Neden |
|---|---|
| Bölgesel talepte **iki görünüm**: Harita (il kutuları) ve **Matris** (il × eğitim türü) *(Sets: "ihtiyaç var")* | Harita "nerede", matris "nerede VE hangi eğitim" sorusunu cevaplar; açık eğitim tam bu kesişimde planlanır. Hücreye tıklayınca sorgulama iki koşulla açılır |
| **"Bugün ne yapmalıyım?" kartların ÜSTÜNE** taşındı *(Sets: "veri kartı veriyi gösterir, cümle listesi anlaşılır kılar")* | Aynı kartlardan üretiliyor, ikinci bir kaynak yok; birkaç satırla sınırlı — özet, özetlediği şey kadar uzunsa ikinci bir gelen kutusudur |
| Eğitmensiz eğitim için **ikinci uyarı şeridi açılmadı** | Tek şerit kuralı korundu; tabloda kırmızı satır + günlük yönetici bildirimi yeterli sayıldı |
| **Global arama ertelendi** | "Ayrı tur, en son her şey bittikten sonra her yere bağlayarak tek seferde temiz çıkartırız" |
| KPI trendi için **günlük** snapshot *(Sets)* | Aylık satırdan gün çıkarılamaz, günden ay çıkar; "son 45 gün" de böyle mümkün olur |

**`32` müşteri kartı — CRM merkezi (02.08.2026)** — matris: `docs/reviews/tasarim-32-kapsama.md`

| Karar | Neden |
|---|---|
| **Müşteri segmenti**: backoffice'te tanımlı liste (ad + ton + not), müşteriyi ekleyen personel atar; ekranda **renk noktası + ad** *(Sets, 02.08.2026)* | "Rozet kafa karışıklığı yaratabilir." Renk tek başına bilmece; ad her zaman yazılı, renk ikinci sinyal. Hangi segmentlerin olduğu admin verisi (AD-08) — kod "A/B/C" bilmez |
| **Sinyalden teklif doğmaz**; ama "müşteri telefonla istediyse" teklif ekranı firma + ihtiyaç başlığıyla açılır *(Sets senaryosu)* | Talep müşteriden gelir; sistem yalnız satışçıyı ikisinin de bildiği şeyi yeniden yazmaktan kurtarır. Kalemleri yine satışçı ekler |
| **Aktivite kendi sayfasında** (`/musteriler/{id}/aktivite`), sekmeler **süzmeden önce sayar** | Kart "nerede kalmıştık"ı dört satırda; sayfa "bu firmayla ne oldu"yu. İki soru, iki ekran |
| Karttan **not** ve **görev**: teklif ekranıyla aynı makine (`RecordNote`, `OpenTask`) | Tasarım `31` kuralı: not/görev tekliften, müşteriden ya da hiçbir şeyden asılabilir — hepsi tek listede buluşur |
| Uyum sinyalinde **isim yok** | Kim oldukları sorgulama ekranının cevabı; orası erişim logu yazar (AD-21) |
| **Müsaitlik şeridinde sertifikası dolmuş eğitmen görünmez** *(Sets, 02.08.2026)* | "Belgesi dolan kişi eğitmen değildir, belge güncellenene kadar eğitmen statüsünde sergileyemeyiz." ⚠ Planlama takviminde görünmeye devam eder — üzerinde işi varsa o iş taşınmalı, satırı gizlemek sorunu gizler |

**`63` kapanışı (02.08.2026)** — matris: `docs/reviews/tasarim-63-kapsama.md`

| Karar | Neden |
|---|---|
| Kullanıcı listesinde **ROL kolonu**: yerleşimi olan için "dikey seviye · düğüm", eğitmen için "Eğitmen · kategori" | Rol adı bir kompozisyondur (AD-23); eğitmenin yerleşimi yoktur, çalışma biçimi onun rolüdür |
| Hesap türü sekmeleri: **Tümü · 3E ekip · Eğitmen** | Eğitmen `users` üzerinde bir dosya taşıyan ofis kimliğidir. **Müşteri kullanıcısı sekmesi yok** — `customer_users` tenant kapsamlı, tek sorguda yüz kiracı okunmaz (63-a, Sets'e soruldu) |
| Eğitmen satırında ad altına **bakanlık numarası**, e-posta yerine | Bildirimlerde o kişi böyle anılıyor ve listede aranan şey bu |
| Kullanıcı detayına **üç kokpit kartı**: geciken işler · bu hafta · çeyrek | Tasarımın "kişinin kokpiti" dediği şey; besleyen modüller artık yazılı |
| **"Hedef %81" yazılmadı** | Hedef admin-tanımlı bir veridir, öyle bir kolon yok; uydurulmuş paydayla yüzde = kırmızı çizgi 9. Kart neden göstermediğini yazıyor |
| Performansta "tamamlanan" **müşterinin onay tarihine** göre çeyrek içi; "potansiyel" dönemsiz | Çeyreğin kazancı içinde evet denenlerdir; boru hattı bir durumdur, bir dönem değil |
| Sistem günlüğüne **kullanıcı · modül · tarih** süzgeçleri, satırda **kullanıcı adı** ve **Detay** bağlantısı | Süzgeçsiz log bir kez okunur; modül olayın kendi öneki, ikinci bir liste tutulmaz |
| Sistem günlüğü **dışa aktarımı erişim logu yazar** | Bir sayfayı okumakla tüm izi binadan çıkarmak aynı şey değil (AD-21) |
| **Ofis sistem günlüğü yalnız ofis kimliklerini taşır; müşteri kullanıcılarının işlem izi backoffice'te** *(Sets, 02.08.2026)* | "ofis kullanıcı logları olacak sadece, müşteri loglarını şu an için sadece backoffice tarafında Filament'e koy." Ofis okuyucusu bu satırları zaten reddediyordu (`OfficeAuditFeed`); backoffice'e **İşlem günlüğü** sayfası eklendi (`WriteLog`, `audit.view_all_tenants`) |
| **Bekleyen onay süreyi aşınca silsile uyarılır** *(Sets, 02.08.2026)* | "bağlı olduğu tüm yöneticiler silsile şeklinde uyarılmalı; en üst yetkili operasyon direktörü." `EscalateWaitingApprovals` saatlik koşar, `ManagementChain::above()` en yakın yöneticiden köke kadar tırmanır; ilk yöneticide durmak, o yönetici izindeyken eskalasyonu da durdururdu. Yönetici başına tek görev (idempotent) |
| ⛔ **Geliştirme makinesi delil deposuna mühür yayımlamaz** *(Sets, 18.08.2026 — EGT-99)* | Ölçüldü 10.08: `.env.example` production değerlerini taşıyordu, bu makine gerçek anchor deposuna **24 mühür** yayımlamıştı (26.07 · 09.08 · 10.08) ve production zinciri bugün **onların üzerine kurulu** — silinemezler, silinmemeli. Karar: geliştirme, **diskteki boş bir depoya** mühürler (`~/.local/share/3e-egitim/audit-anchor-dev.git`); hat aynen çalışır, hiçbir şey dışarı çıkmaz. ⛔ Sebebin kendisi **anahtar zorunluluğuydu**: tanık anahtarsız çalışmayı reddediyordu ve elde tek anahtar delil deposunun dağıtım anahtarıydı — artık **yerel yol için anahtar istenmiyor**, uzak adres için istenmeye devam ediyor. ⚠ Depodaki 24 girdi için README notu **yazılmadı** *(Sets: "gerek yok — tarihleriyle geliyorlar; sorulursa cevap net, depo testleri de barındırıyor, silinemez olduğu için müdahale etmedik")* |
| ⛔ **"İş günü" diye bir hesap YOK; bütün süreler takvim günü sayılır** *(Sets, 18.08.2026 — EGT-43 `Won't fix`)* | *"Resmî böyle bir liste yok, böyle bir yapıya ihtiyacımız da yok. Lojistik sektörü 7/24/365 çalışan bir sektör zaten; tatil günü ise o gün kendileri ayarlarlar iş akışlarını."* Resmî tatil takvimi **hiç yazılmayacak**. Etkilediği üç yer: onay eskalasyonunun "1 gün"ü (AD-23), bakanlık bildirim süresi (`NotificationDeadline`, eğitim tanımındaki gün sayısı) ve eğitmen tarafındaki "3 gün"ler (`egitmen-spec` §6.7). ⚠ Bildirimde takvim günü saymak son tarihi gerçeğinden **erken** getirir, geç değil — cezası olan bir kuralda doğru yön budur. Beş dokümandaki "EGT-43 bekleniyor" notları karara çevrildi |

---

### Kullanıcı ekranları — üç soru, üç sayfa (EGT-70, 30.07.2026)

*Sets: "detay sayfasında kullanıcı detayları olur… yetki sayfasında yetkiler, log sayfasında loglar."*

| Sayfa | Ne var |
|---|---|
| `/kullanicilar/{id}` | **Kimlik**: ad, e-posta, telefon, doğrulamalar (e-posta + ikinci adım), son giriş, durum — **düzenlenebilir** (`user.update`) |
| `/kullanicilar/{id}/yetkiler` | Yetki matrisi (EGT-68) |
| `/kullanicilar/{id}/loglar` | Kişinin işlem günlüğü, aramalı |

⛔ **E-posta değişirse doğrulama sıfırlanır.** Adres hem giriş kimliği hem teslimat adresi; eski doğrulamayı taşımak, kanıtlanmamış bir adrese başkasına ait kanıtı devretmek olurdu. Ekran bunu **değişiklik yapılırken** söylüyor.

⚠ **Düzenleme `user.update`'e bağlı, `user.assign_permissions`'a değil** — meslektaşın telefonunu düzeltmekle yetki dağıtmak aynı güveni gerektirmez (27.07.2026 ayrımı).

**Liste satırı: üç kapı, iki karar.** Detay/Yetkiler/Loglar bir yere *gider*, hiçbir şeyi değiştirmez (ghost). Dondur **amber ve geri alınabilir**, Kapat **kırmızı ve nihai**; aralarında ayırıcı çizgi, önlerinde glif. Beş özdeş ghost buton "kapat"ı "detay" kadar sıradan gösteriyordu. ⛔ **Kendi satırında dondur/kapat yok** — eylem zaten reddediyor, sadece reddedilecek bir buton gösteriyordu.

---

### Yetki ekranı — `64` uygulandı (EGT-68, 30.07.2026)

**Ayrı sayfa** `/kullanicilar/{id}/yetkiler`; kullanıcı detayında yalnız özet + kapı durur. Gerekçe: matris drawer'a sığmıyor ve bu ekran **gerekçe ve denetim üreten** bir iş — drawer "hızlı düzeltme" vaat eder.

| Karar | Nerede |
|---|---|
| **Kapsam pili** — checkbox+select yerine tek kontrol; seçenekler hücreye göre (ekleme yetkilerinde `kendi` yok); **cycle değil menü**, çünkü fazladan tık yetki genişletir | `molecules/ScopePill.vue` |
| **Kapalılığın üç dili** — `—` (o kaynakta yok) · `sende yok` (Y-4, kime sorulacağı yazılı) · `rolden` (kilitli; ofiste oluşmaz, müşteri tarafı için durur) | `organisms/PermissionMatrix.vue` |
| **Y-4 okunur hâle geldi** — hücre kapalı doğar, kaydederken reddedilmez. Sekiz tiki bir anda kaybetmek ekrana güveni bitirir | `Support/Authorization/Ceiling.php` |
| **Grup üyeliği bloğu + `⤍` işareti** — personel seviyesi `grubu` kapsamını fiilen `kendi`ne daraltıyor; matris bunu gizliyordu | `Queries/Office/OfficeUserMemberships.php` |
| **Matris dışı 9 karar dört başlıkta**: görüş alanı · hassas veri · onay ve karar · süreç işlemleri | `PermissionMatrix::STANDALONE_GROUPS` |
| **Kaydetmeden önce diff** — verilen / alınan / **kapsam değişikliği ayrı**; opsiyonel gerekçe denetime yazılır | `pages/Office/Users/Permissions.vue` |
| ⛔ **Son yetki-devri sahibi korunur** — yoksa ofis kendi yetkilerinden kilitlenir, tek dönüş yolu backoffice | `AssignOfficePermissions::refuseLockingEverybodyOut()` |

**Yetki şablonu kararı (Sets, 30.07.2026): tek seferlik kopya.** Şablon uygulanır, yetkiler kişiye yazılır, **bağ kopar**; şablon sonradan değişirse kimse etkilenmez. Kalıcı bağ seçilseydi ofise fiilen **rol** girecekti — AD-23'ün "seviye yetki taşımaz" ilkesiyle çelişirdi. Bu yüzden `permission_templates` ile kullanıcı arasında foreign key **yoktur ve olmamalıdır**; uygulamadan geriye kalan, şablonu adıyla anan denetim satırıdır.

**İkinci tur (EGT-69, aynı gün):**

| Kalem | Karar |
|---|---|
| **Kapsam pili chevron** | Diğer selectlerle aynı `▼`/`▲`; olmayınca pil durum rozeti gibi okunuyor, açılabildiği keşfedilmiyordu |
| **Matris dışı kararlar toggle** | Tasarımın çizdiği gibi: ızgara hücresi elli tanenin biri olarak taranır, tek başına duran karar tek başına okunur — toggle "birinin çevirdiği anahtar" der |
| **Üyelik ekle / çıkar** | Yetki sayfasında, `user.assign_permissions` ile — çünkü yerleştirme kişinin **mevcut** kapsamlı yetkilerinin menzilini değiştirir; matrisin gösteremediği yarı budur |
| **Şablondan uygula / kişiden kopyala** | İkisi de yalnız **ekranı** doldurur; diff yine okunur. Y-4 uygulanır ve **getirilemeyenler listelenir** — sessizce inceltmek, paketi verdiğini sanan birini geride bırakır |
| **Grup ağacı** | Dört dikey kök olarak seed edildi (Operasyon · Satış · Planlama · Eğitmen kadrosu); alt düğümler backoffice'ten |
| **Yeni kullanıcı 3. adımı** | Sihirbaz sayfası değil, çekmecede **iki çıkış**: *Kaydet ve Yetkilendir* (yeni kişinin yetki ekranına götürür) · *Kaydet* (yetkisiz davet). Matrisi çekmeceye koymak, `64` turunun kaçındığı şeklin ta kendisiydi |
| ⛔ **Bulunan hata** | Kullanıcı ekleme çekmecesi `/yonetim/kullanicilar`'a POST ediyordu — hiç var olmamış bir adres. Listeden kullanıcı eklemek **404 veriyordu**; hiçbir test yakalamadı çünkü hepsi kullanıcıyı action üzerinden yaratıyordu. Düzeltildi ve endpoint testi yazıldı |
| ⛔ **İkinci bulunan hata** | Matris **dışı** kararlardan **kapsam taşıyan beşi** backoffice'ten hiç sunulmuyordu: bölüm base adı (`training.approve`) ile grantable listesini (`training.approve.all`) kesiştiriyordu, kesişim boştu. Maliyet onayı · iptal · iptal onayı · eğitmen atama · bakanlık bildirimi — ilk ofis hesabını açan tek ekranda yoktu ve hiçbir şey bunu söylemiyordu (dokuz yerine dört satır çiziyordu). Operasyon direktörünün bu yetkileri bu yüzden yoktu |
| **Backoffice hizalandı** | Kapsamlı standalone yetkiler artık Filament'te de **select** — ofis ekranındaki pilin dört durumu. İki panel, tek sözlük |
| **Şablon içerikleri** | Dört taslak paket seed edildi — **taslaktır**, backoffice'ten düzenlenir. Eğitmen paketi neredeyse boş: eğitmen panelini açan şey yetki değil, eğitmen kaydıdır |

---

### Finans — Sets'in tanımı (30.07.2026)

Uzun süre açık kalan dört soru cevaplandı. **Spec turu bu dört karar üzerine yazılacak.**

**1 · Eğitmen carisine elle kayıt girilebilir** *(egitmen-spec §7.7 düzeltildi)*. Spec *"elle borç satırı açılmaz"* diyordu; kastedilen *"cari birinin hatırladıklarından oluşmaz"*, *"saha gerçeği sisteme girilemez"* değildi. Saha gerçeği: *"patron nakit aldı, üstünde olmadığı için şirkete 'carisine elden nakit alındı diye girin, ben onaylarım' dedi."* Değişmeyen ilke: **her cari satırının arkasında sistemde bir kayıt var** — notu, gireni, zamanı, denetim izi ve **onayı** ile.

**2 · Müşteri carisi faturadan DEĞİL, kendi kayıtlarımızdan doğar.** *"Fatura bizim üzerimizden kesilmeyebilir… mali tabloyu takip etme şansımız yok, banka tarafına erişemiyoruz."* Takip edebildiğimiz iki şey: **teklif onaylandı mı** (sistemde) ve **eğitim gerçekleşti mi** (yoklama/evrak yüklendi, sistemde). Müşteri tarafı bu ikisinden yürür. Ayrıca **bağımsız fatura kaydı** tutulabilir (danışmanlık, hizmet) — ileride e-fatura entegrasyonu o bölümden ilerler. ⚠ Yani banka gerçeği değil, **3E'nin kendi görüşü** izlenir; entegrasyon gelirse yeniden düzenlenir.

**3 · Doğuş anları:** masraf **onaylandığında**; eğitmen hakedişi **eğitim gerçekleşip evrakları eksiksiz yüklenip onaylandıktan** sonra. İkisi de onay sonrası cari'de görünür.

**4 · Eğitmen dosyası üç sayfa** — kişilerdeki desenin aynısı (tab değil, sayfa):

| Sayfa | İçerik |
|---|---|
| `/egitmenler/{id}` | Kimlik **(düzenlenebilir — `65`'te eksik)** · verebildiği eğitimler · evraklar · yaklaşan atamalar |
| `/egitmenler/{id}/finans` | ⛔ **Gerçekleştirdiği eğitimler ile cari AYNI TABLO**: her eğitim satırı altında o eğitimin hakedişi ve masrafları, altta net. Üstte üç rakam (borç · ödenen · açık). Eğitime bağlı olmayan hareketler (elden nakit, *"diğer"*, ödemeler) ayrı bölüm |
| `/egitmenler/{id}/loglar` | Kişinin işlem günlüğü |

⚠ **Dördüncü bir takvim sayfası yapılmayacak:** eğitmenin kendi takvimi (`/egitmen/takvim`) ve planlamacının takvimi (`23`) var; operasyonun *"bu eğitmen önümüzdeki hafta nerede"* sorusu `23`'ün eğitmen satırıdır. Dosya sayfasında yaklaşan atamalar bloğu yeterli.

⚠ **Satışçı finansı cari değildir** — potansiyel/hedef (`62`'deki "satışçı potansiyelleri"). Aynı ekranda göstermek borç-alacak ile beklentiyi karıştırır.

---

### Tasarımcıya verilen brief'ler (30.07.2026)

| Brief | Kapsam | Neden tasarımcıya |
|---|---|---|
| `docs/design/yetki-ekrani-brief.md` | Yetki düzenleme | Teslim edildi → `64`, uygulandı (EGT-68/69) |
| `docs/design/tedarikci-brief.md` | Tedarikçi liste + ekleme + **detay** | `23` yalnız listeyi çiziyordu; indirim, notlar, anlaşma+imzalı nüsha, fiyat birimi/miktar, ilçe geliştirme sırasında eklendi — beş kalemin çizilmiş kompozisyonu yok |
| `docs/design/egitim-planlama-brief.md` | Eğitim planlama **tek sayfa** (mevzuat dahil) | Teslim edildi → **`24 Planlamaci Egitim Planlama`** |
| `docs/design/tedarikci-brief.md` (yukarıda) | | Teslim edildi → **`25 Planlamaci Tedarikciler`** |
| `docs/design/egitmen-kayit-brief.md` | Eğitmen ekleme + dosyanın yetkinlik bölümü | Teslim edildi → **`65 Yonetici Egitmen Kayit`** |
| `docs/design/egitmen-dosyasi-brief.md` | Eğitmen dosyası **üç sayfa** (künye düzenleme · finans · loglar) + **hızlı önizleme bileşeni** | `65` ekleme sayfasını ve yetkinliği çizdi; kimlik düzenleme ve finans eksik. Önizleme `66`'da çizili — **aynı bileşen** dosya/finans/onaylarda kullanılacak, üç ayrı önizleme üç ayrı loglama hatası demek |
| `docs/design/onaylar-brief.md` | Bekleyen onaylar ekranı | Teslim edildi → **`66 Yonetici Onaylar`** |

**Fiyatlama durumu (30.07.2026 kontrolü):** spec **yazıldı** (`docs/specs/fiyatlama-spec.md`, 28.07.2026, Sets'in tanımından) ve **backoffice tarafı uygulandı** — `price_groups` (grup + yüzde), `price_lists` (`kind`: günlük / kişi başı, `effective_from/to`, `published_at` = sürümleme), `price_bands` (saat bantları), `prices` (`is_overridden` = hücre ezmesi), `customers.price_group_id`, Filament kaynakları ve fiyat matrisi ekranı + testler. Eksik olanlar **satış modülüne bağlı**: teklifte fiyat/indirim (§4), satışçı başına özel indirim limiti, müşteriye grup atama ekranı (`32` satışçı müşteri kartı). Ayrı bir plan dosyası yok — işler o turun planına gömülmüş.

⛔ **Eğitmen kategori taban tarifesi eksik.** `egitmen-spec` §4.2 *"baz ücret kategori başına tanımlanabilir; backoffice'te Eğitmen ayarları sayfası bunları tutar"* diyor — **ne ayar ne ekran var**. Eğitmen kaydında tarife boş bırakılırsa fallback edecek bir değer yok. §5'in masraf ayarları (km bedeli, kalem limitleri, fiş zorunlulukları) da aynı sayfaya ait ve yok. Kapanış adresi: **Eğitmen ayarları** sayfası.

⚠ **Belge görevi süresi ayar oldu (Sets, 30.07.2026):** `instructor.document_task_days`, varsayılan **10 gün** (yedi takvim günü boşluksuz bir iş haftası; istenen kişi genelde yolda). Backoffice *Eğitmen ayarları* bölümünden değiştirilir. Daha önce belge görevleri **tarihsiz** açılıyordu — hiç gecikmiş sayılmıyordu, yani kimse kovalamıyordu.

⚠ **Test kadansı revizyonu (Sets, 30.07.2026):** kalem üzerinde çalışırken yalnız dokunulan şeyin testi (`--filter`), commit öncesi tam koşu, ticket kapanışında CI. Ölçülen sebep: tek sayfalık düzeltme, ~1200 testi koşmak yüzünden ~20 dakika sürdü.

---

### Tasarım kuralı — düzeltildi (Sets, 30.07.2026)

*"Nihai değildir"* **"umursamayın" demek değildi.** Tasarımcının teslim ettiği **eksiksiz uygulanır** — sayfa kompozisyonu, kartlar, ekran bölünmesi, menü grupları dahil. *"Ama tasarımcı böyle yapmış"* denerek hedeften sapılmaz; iyileştirme/düzenleme **yalnız Sets söylediğinde** yapılır (örnek: 5 adımlı sihirbazın tek sayfaya çevrilmesi). Kural `CLAUDE.md` ve `design/HANDOFF.md`'de güncellendi.

**Ölçülen maliyet:** input'lar, sayfa düzeni ve sidebar defalarca geri aldırıldı; `40 Egitmen Paneli` açılmadan kokpit beş sayfaya bölündü (tasarımcı zaten bölmüştü, farklı bölmüştü); `02 Veri Gosterimi`, `22 Planlamaci Egitim Detay`, `63 Yonetici Kullanici Log` hazırdı, kullanılmadı.

#### Kural yetmedi → **protokol** yazıldı (Sets, 31.07.2026)

Sets: *"tasarım konusunda seni uyarmaktan çok sıkıldım, 50 kere revize veriyorum."* Kural üç yerde yazılıydı ve `65`'te yine tutulmadı — çünkü yazılı olan **niyetti, prosedür değildi**. Teşhis: spec ve bileşen kataloğu okunuyor, **ekranın kendi kompozisyonu okunmuyordu**; alanlar doğru çıkıyor, yerleşim uydurma kalıyordu (`65`'te Bakanlık ID ile sertifika geçerlilik aynı satırdaydı, alt alta yazılmıştı).

`CLAUDE.md` → **Tasarım Uygulama Protokolü**, üç adım: (1) dosyanın tamamını oku ve ızgara/alan sırası/etiket metni/alan içi ek/rozet/kontrol sayısı envanterini çıkar, (2) tasarımcı bir kontrol çizdiyse **atomu yaz** — sayfaya gömme, yaklaşığını kullanma, (3) teslimden önce **tasarım öğesi | ✓/⚠/❌ | kanıt (dosya+satır)** matrisi; bellekten üretilen matris geçersiz.

**Aynı turda çıkan eksik atomlar:** aşağı ok göstergesi (native select'in oku platforma göre değişiyor, bazılarında hiç çizilmiyor → **liste açan kontrol açmayanla aynı görünüyordu**), kompakt telefon (kapalıyken `+90`, açıkken `+90 Türkiye` — geniş select alanın üçte birini yiyor ve `65`'in telefon+kategori tek satırını imkânsız kılıyordu), hassas alan girişi (kilit **değerin başında**), takvim işareti, mevzuata göre gruplanmış çoklu seçim, `FormField` footer slotu.

**Tasarım ↔ kod farkı — 30.07.2026 akşamı, günün işlerinden sonra:**

| # | Tasarım | Durum | Eksik olan |
|---|---|---|---|
| `00`–`06` | Tasarım sistemi | ✓ | Token seti, form elemanları, segmented control, kilit işareti bileşen oldu |
| `10` | Ortak auth | ✓ | — |
| `11`–`16` | **Müşteri portalı** (dashboard, personel, teklif, teklif isteği, eğitimlerim, belgeler) | ✓ | Altısı da yazıldı (chunk 1–4). `16` içerik paketleri + kısmi tahsilat revizyonuyla güncel |
| `20` | Planlamacı sorgulama | ⚠ | Ekran yazıldı (#72, 02.08.2026): filtreler, firma bazında katlama, erişim logu, görev açma. **Eksik: kayıtlı görünümler, dışa aktarma, "Bölgeye Duyuru Aç"** — matris `tasarim-20-kapsama.md` |
| `21` → `24` | Planlamacı planlama | ✓ | `24` tek sayfa (EGT-72): mevzuat kartları, katlanabilir satırlar, uyarı paneli, paket kartı |
| `22` | Planlamacı eğitim detayı | ⚠ | Statü şeridi, geçmiş, katılımcı tablosu, maliyet kalemi formu yazıldı. Eksik: **Yoklama Listesi (PDF)** butonu ve "Eğitmene Sor" — matris `tasarim-22-kapsama.md` |
| `23` / `25` | Planlamacı takvim + tedarikçiler | ✓ | Takvim (#44) ve tedarikçi liste/detay (EGT-72, #50) yazıldı; matris `tasarim-23-kapsama.md` — tek bilinçli sapma boş hücrede eğitmen ön seçimi |
| `30`, `33` | Satışçı gündemi ve takvimi | ✓ | Kokpit ve takvim yazıldı (02.08.2026); matris `docs/reviews/tasarim-30-33-kapsama.md` |
| `31` | Satışçı teklif ekranı | ✓ | EGT-74: teklif, sürümler, ekler, CRM yüzeyi, paket satırı |
| `32` | Satışçı müşteri kartı | ✓ | CRM yüzeyi yazıldı (02.08.2026); matris `docs/reviews/tasarim-32-kapsama.md` |
| `35` | İçerik paketleri | ✓ | Katalog, paket detayı, müşteri vitrini ve teklife paket ekleme (#66) |
| `36` | Gündem deseni | ✓ | Planlamacı + operasyon (#67); `3a` kokpit genişlemesi (#42) — matris `tasarim-3a-kapsama.md` |
| `37` / `38` | Teklif ekleri · kenar hâller | ✓ | #69 ve #70; `38`'de yalnız doğal dil tarih çıkarımı (B7-a) kapsam dışı |
| `40` | Eğitmen gündemi | ✓ | Matris `tasarim-40-kapsama.md` — açık satır yok |
| `41` | Eğitmen saha | ✓ | Matris `tasarim-41-kapsama.md` |
| `50` | **Katılımcı paneli** | ❌ | **Hiç yok.** Bugün yalnız erişim kuralını kanıtlayan bir JSON uç var (`ParticipantAreaController`). Magic link karşılama, kimlik teyidi (TCKN son 4), KVKK açık rıza, oturum beyanı, sertifika görünümü yazılmadı |
| `51` | Anket ve yoklama | ✓ | #68: anket veri katmanı, portal ekranı, sonuç ekranı, A4 yoklama şablonu |
| `61` / `65` / `67` / `68` | Eğitmen kaydı, dosyası, durumu | ⚠ | Eğitmen tarafı eksiksiz (#52, #53, #75, #77). **Satışçı tarafı yok**: `61`'in grup/müşteri/limit kolonları ve satışçı düzenleme draweri |
| `62` | Yönetici muhasebe | ✓ | Mali tablo, müşteri carisi, eğitmen carisi, satışçı performansı yazıldı (02.08.2026); matris `docs/reviews/tasarim-62-kapsama.md`. ⛔ e-fatura entegratörü seçilmedi — tasarımın kendi notu da öyle diyor |
| `63` | Kullanıcılar + log | ✓ | Liste (rol kolonu, hesap türü), kullanıcı kokpiti, sistem günlüğü süzgeçleri + dışa aktarma (02.08.2026). Matris `tasarim-63-kapsama.md` |
| `64` | Yetki düzenleme | ✓ | EGT-68/69 |
| `66` | Onaylar | ✓ | Dört tür tek kuyrukta, kanıt paneli, gerekçeli ret; **SLA eskalasyonu 02.08.2026'da silsile olarak yazıldı** |

⚠ **Tablo 02.08.2026'da güncellendi.** Kalan ❌'ler artık veri katmanı eksikliği değil, yazılmamış ekran: **`50` katılımcı paneli**, **`30`/`33` satışçı gündemi ve takvimi**, **`62`'nin mali tablo/cari/e-fatura kısmı**, `61`'in satışçı kolonları, `20`'nin kayıtlı görünüm + dışa aktarma + duyuru çıkışları.

---

### Müşteri portalı ve teklif dikeyi — kararlar (EGT-74, 31.07.2026)

Sets: *"müşteri tarafını eksiksiz yapalım, teklif dahil."* **Üç aşama da APPROVED** (31.07.2026):

| Aşama | Belge | Heyet | Tur | Bulgu |
|---|---|---|---|---|
| Brainstorm | `docs/design/musteri-portali-brainstorm.md` — **33 karar** | 170 ✅ | 4 | 16 |
| Spec | `docs/specs/musteri-portali-spec.md` — **48 kriter** | 173 ✅ | 3 | 10 |
| Plan | `docs/plans/musteri-portali-plan.md` + 4 chunk | 177 ✅ | 4 | 13 |

**Sıradaki: implementasyon**, chunk sırası 1 → 2 → 3 → 4.

**Neden teklif dikeyi dahil:** çatı §10 tek yönlü değil — satışçı hazırlar → 3E yöneticisi onaylar → firmaya gider → firma panelden onaylar. Müşterinin `13` ekranı bu zincirin **alıcı ucu**; üreten uç (`31`) olmadan gösterecek şeyi yok.

| Karar | Özü |
|---|---|
| K-13/K-14 | Bir teklifte **günlük ve kişi başı fiyat karışabilir**; bir teklif **N eğitime** yayılır (6 aylık toplu teklif) |
| K-18/K-19 | Satışçı **eğitim yaratamaz**, **talep açar**. Toplu teklif de bunu bozmaz: müşteri toplu ister → planlamacı planlar → satışçı planlanmış N eğitimi tek teklifte toplar |
| K-20 | **Kontenjan tahsisi eğitim bazlı** — onay tek, rezervasyon kalem kalem. Biri dolmuşsa o kalem tutmaz, teklifin tamamı düşmez |
| K-22 | **Teklif ofiste yaşar, müşteriye açılır.** *"İki kimlik dünyası tablo paylaşmaz"* kuralı **kimlik tablolarınadır**, iş verisine değil — eğitmen müşterinin katılımcılarını görür, müşteri satışçının teklifini görür. Ortak yüzey kaçınılmaz; cevap ayırmak değil **kapsam kontrolü + izolasyon** |
| K-23 | **Bağımsız katılımcı ayrı model gerektirmez** — müşteri olarak eklenir; `customers.legal_type` zaten `legal_entity`/`natural_person` taşıyor |
| K-24 | **Maille gelen cevap görünür olur ve bildirim üretir** — kaydın durumunu değiştirmez; aksiyonu satışçı alır. ⛔ Kabul onay değildir (AD-20 §1) — onay yalnız panelden. ⚠ **AD-20 revizyonu gerekmedi** (Sets, 31.07.2026) |
| K-25 | **Faturayı doğuran olay ONAYDIR**, eğitimin gerçekleşmesi değil. Kısmi/eğitim-gerçekleştikçe faturalama yok |
| K-26 | **"Açık eğitim" satışçılara görünürlüktür, müşterilere değil.** Müşterinin önüne plan/eğitim listemiz asla düşmez; tanıtım mailing |
| K-28 | Bekleyen işte yalnız süre değil **son hamle** de görünür (kim, ne zaman, ne dedi) + satırdan hatırlatma görevi |

⛔ **Onaylı tasarım sapması:** `16 Musteri Belgeler`'deki *"Açık eğitim duyuruları · `/portal/acik-egitimler`"* bloğu **kapsam dışı** (K-26). `16`'nın geri kalanı geçerli. ⏸ Ertelenen: planlama/talep sırasında *"müşterilere de görünsün"* seçeneği — o gün görünürlük üç değerli olur.

⛔ **Katılımcı omurgası kapsama alındı** (Sets: *"backlog oluşturmayalım"*): oturum · kayıt · yoklama · beyan · sınav · tamamlama · **sertifika KAYDI**. Aynı omurga `41`'i, hakedişi (#49) ve `40`'ın kalan boşluklarını da açıyor. ⚠ Sertifikayı **Bakanlık üretir** (çatı §136) — sistemde yalnız numara + veriliş + geçerlilik; belge tutulmaz, indirilmez.

⛔ **`16`'daki "İndir (PDF)" ve "QR doğrula" UYGULANMADI** *(Sets, 31.07.2026 — soruldu, onaylandı)*. Tasarımcı sertifika kartına iki eylem çizmişti; ikisi de 3E'yi belgenin sahibi gibi gösteriyor. **Doğrulamayı müşteri Bakanlıktan yapar**; kafa karışıklığı yaratmamak için tedbiren kaldırıldı. Uygulanan: numara + tarihler + **zorunlu ibare**, ve ibare `CertificateRow` bileşeninin kendi içinde yazılı — prop olsaydı bir sayfa unutur, orada müşteri sertifikayı 3E'nin verdiğini sanardı.

⛔ **`12`'deki "Excel'den Aktar" KAPSAMDA** *(Sets, 31.07.2026)*. Neyin aktarımı olduğu soruldu: **firmanın kendi personeli** (`/portal/personel` başlığında, "+ Personel Ekle"nin yanında) — eğitim katılımcı listesi değil. 48 kişilik bir firmanın drawer'ı 48 kez açması beklenemez. ⚠ Tasarımcı yalnız **düğmeyi** çizdi, arkasındaki ekranı çizmedi; akış (yükle → satır satır doğrula → ne olacağını göster → onayla) bizim kararımız.

⛔ **P-9 uygulandı ve iki tuzağı ölçüldü** *(31.07.2026)*. `training_requests` artık `tenant_id IS NULL` satır taşıyor (satışçının talebi). Naif politika — `tenant_id IS NULL OR tenant_id = ANY(app.tenant_ids())` — müşteri oturumuna da açık kalıyordu; `app.is_office()` eklendi, kimlik alanı `TenantContext` içinde `SET LOCAL` ile yazılıyor, fail-closed. ⚠ İki şey yazarken çıktı: (1) `app.tenant_ids_or_empty()` **zaten vardı** ve `CREATE OR REPLACE` ile üzerine yazılmıştı — rollback yakaladı, artık yeniden kullanılıyor; (2) boş kapsam çerçevede yasaktı, `TenantContext::forOffice()` olarak birinci sınıf hâle geldi.

⛔ **Talep kodu `TLP` — iki harf kuralının tek istisnası** *(Sets, 31.07.2026, soruldu)*. `TL` her Türk okuyucuya önce **para birimini** düşündürüyor; açıklanması gereken kod, işini yapamamış koddur. Üç harf **izinli, tercih değil**. ⛔ **Son ek değişmedi: her zaman rastgele, asla sıra numarası** — Sets aynı gün tekrar söyledi. `reference_codes.prefix` kolonu 2→3 genişletildi.

⛔ **Talep, dönüştüğü şeyin koduna DÖNÜŞÜR** *(Sets, 31.07.2026)*. Bir talep tek eğitime **ya da programa** dönebilir; ikisinde de `TLP-2026-7K4F` → `PL-2026-7K4F` (tek eğitim) veya `PR-2026-7K4F` (program, eğitimleri `PL-2026-7K4F-01`). Son ek talep açıldığında **rezerve edilmiş** oluyor — `(yıl, son ek)` her önek için benzersiz. Eski adres `previous_code`'da yaşamaya devam ediyor, yani `tlp-…@` adresine gelen yanıt aynı kayda düşüyor.

⚠ **"Plana Çevir" `24`'ü dolduruyor:** talebin eğitimleri satır satır açılıyor, il/tarih/kontenjan/görünürlük/firmalar öneri olarak geliyor — hepsi düzenlenebilir, çünkü tarih ve eğitmen planlamacının kararı. Kaydetme `PlanTrainingRequest`'ten geçiyor: talep `planned` oluyor ve kod taşınıyor.

⛔ **`training_request.create` / `.update` izinleri EKLENDİ** *(31.07.2026)*. Aile yalnız `view` taşıyordu. Satışçı talep açar ama **eğitim yaratamaz** (çatı, Sets 30.07): `training.create` yeniden kullanılsaydı her satışçıya planlamacı yetkisi verilmiş olurdu. Plana çevirme `answer` yeteneğinde kaldı — o gerçekten eğitim yaratır.

**Yeni tasarım brief'i:** `docs/design/planlama-talebi-brief.md` — satışçının talep ekranı (çoğul eğitim, il, tarih aralığı, açık/kapalı, davet edilecek firmalar) + talep kuyruğu. ⚠ Mevcut `training_requests` bu şekli taşımıyor: `tenant_id` NOT NULL + RLS, tek tanım, serbest metin lokasyon. Talebin **sahibi değişiyor**.

---

### Bildirim kuralları: kim × ne zaman (Sets, 01.08.2026)

⛔ **Bu parametreleri kod yazmaz, müşteri belirler.** Sets: *saha tecrübesi onlarda — eğitimi iki ay önceden
planlayıp teklif aşamasına getirmeleri gerekiyor, belki dönüp "5 ay önceden görelim" diyecekler.* Biz varsayılanı
veririz, onlar düzenler.

⛔ **Ve tek bir bant listesi yetmiyor — çünkü muhatap değişince süre de değişiyor.** Sets'in örneği kararın kendisi:
*sertifika bitişini satışçıya 3 ay önceden bildir ki müşteri başka firmaya gitmeden teklif verebilsin; katılımcıya ve
müşteri yöneticisine 1 ay önceden yeter.* Aynı olay, farklı taraflar, farklı saat.

Bu yüzden ayar birimi **kural**dır: `notification_rules` → **olay × muhatap × bantlar**.

| Muhatap | Ne demek |
|---|---|
| `participant` | Kaydın ait olduğu kişi |
| `customer_manager` | Firmanın portaldaki uyum sorumlusu |
| `salesperson` | O firmanın satışçısı |
| `instructor` | Belgenin sahibi eğitmen |
| `office_group` | Adı geçen ofis grubu (grup zorunlu) |

Kurallar:

- ⛔ **JSON'a gömülmedi, tablo oldu.** Ucuz olan `settings` içinde bir blob'du ve yanlıştı: listelenemez,
  satır satır doğrulanamaz, satır satır denetlenemez, tümünü yeniden yazmadan kapatılamaz. `settings` "KDV %20"
  içindir; bu küçük bir modeldir.
- ⛔ **İzin listesi DEĞİL.** İzin "ne yapabilir"i söyler, "neyi bilmeli"yi değil; bildirimi izne bağlamak, bir
  meslektaşa okuma yetkisi vermenin onu ayda bin maile abone etmesi demektir.
- ⛔ **`office_group` kuralı grupsuz olamaz** (veritabanı CHECK'i + action reddi): grupsuz kural kimseyi uyarmaz ama
  ekranda çalışan bir kural gibi görünür — ayarların en kötü türü.
- ⚠ **Varsayılanlar bir kez tohumlanır, bir daha sıfırlanmaz** (`firstOrCreate`). Satış uyarısını 3 aydan 5 aya
  çekmiş bir kurulum, deploy yüzünden geri dönmemeli.
- ⛔ **`customer_manager` kapalı gelir.** Bir firmaya "çalışanınızın sertifikası doluyor" demek, o kişi hakkında
  işverenine bilgi vermektir; **açılmış olarak gelmez, birisi açar.**
- ⚠ **Bant sayısında sınır yok** (5 de olur 8 de) — kaç kez uyarılmak istedikleri onların kararı; sınır koymak
  koda gömülmüş bir iş kuralı olurdu. ⚠ `0` meşrudur: "aynı gün uyar".
- ⚠ **Pasif kural silinmez, kapanır** — kime yazıldığı bilgisi korunur. Listeden **kaybolan** kural silinir ve
  silinmeden önce denetim kaydına yazılır.
- ⚠ Sayfada ayrıca: **planlama ufku** (varsayılan 2 ay) ve **tarama saati** (07:00, 0–23'e kırpılır — key/value depo
  25'i reddedemez ve 25. saate kaydedilmiş zamanlama hiç koşmaz).

#### Kalan taramalar, olay bağlantıları, panel ve yazışma (01.08.2026)

**Beş tarama** ortak bir iskelet üstünde: eğitmen belgesi, KUMS son günü, fatura vadesi (öncesi **ve** sonrası),
eğitim hatırlatması. ⚠ Fatura taraması **tek komut iki olay**: işaret farkı — iki komut, tek döngünün bir eksi
işaretiyle ayrılan iki kopyası olurdu ve bakılmayan kopya kayardı. ⚠ Tahsil edilmiş fatura **türetilmiş kalandan**
düşüyor; sabahleyin ödenen fatura ertesi günkü taramadan kimse bir şey kapatmadan çıkıyor.

**Olaylar action'lara bağlandı:** tek alan olayı (`NotifiableThingHappened`) + tek kuyruklu dinleyici
(`ShouldQueueAfterCommit`). ⛔ Action **ne olduğunu** söyler; **kim haber alacak** kurallardan gelir.
⛔ **Kural yoksa kimseye gitmez** — "kural yoksa bariz kitleye gönder" ayar ekranını öneriye çevirirdi: uyarıyı
kapatan kişi onun gelmeye devam ettiğini görürdü.

⛔ **Test bir hata buldu:** `Dispatchable::dispatch()` **statiktir**; instance üzerinde çağırınca argümansız
**ikinci** bir olay kuruyordu. "Bildirim hiç gelmiyor" olarak, ekranda hiçbir iz bırakmadan yayına çıkardı.

**Panel bildirim listesi + tercih ekranı:** liste kaydın kendisidir — worker'ların yazdığı satırlar, **kullanılan
metniyle**. ⛔ Zorunlu bildirimde anahtar **çizilmez, cümleyle söylenir**: hareket edip mailler gelmeye devam eden
bir anahtar, okuyana ürünün bozuk olduğunu öğretir. ⚠ Rozet sayısı her sayfada, liste yalnız panel açılınca.

**Şablon ekranında iki kusur düzeltildi:** anahtar artık **serbest metin değil, kapalı katalogdan seçim** — serbest
metin, hiçbir şeyin göndermediği bir olaya kusursuz bir şablon yazılmasına izin veriyordu (belirtisi olmayan hata).
Ve tekillik **kanalı görmüyordu**: bir bildirimin iki metni var (mail + panel) ve form ikincisini yazmayı
engelliyordu — şemadaki aynı hatanın form tarafı.

**Yazışma aynı hatta alındı** *(Sets: "aynı yapıyı mail ve diğer iletişim alanları için de kur")*. Eski hâl maili
**transaction'ın içinden** kuyruğa atıyordu: sonradan geri alınan bir transaction, satırı yok edip mektubu
gönderiyordu — müşteri, var olmayan bir kayıt hakkında mail elinde. Ve **ulaşıp ulaşmadığı hiçbir yerde yazmıyordu**.
Artık mesaj satırı iş kalemi: `queued` yazılır, aptal worker claim eder, sonucu yazar. ⛔ Gönderim dışı listesi
mektuplara da uygulanır. ⚠ Reply-To **yazılırken** kaydedilir, worker türetmez — kod kaydın referansını taşır ve
plandan eğitime geçişte ön ek değişir (AD-19).

---

#### Bir kişi, bir bildirim (Sets, 01.08.2026)

⛔ **Aynı kişi birden çok kurala giriyorsa yine tek bildirim alır.** Hem katılımcı hem firmasının uyum sorumlusu
olan biri, aynı bantta iki kurala birden uyar; mükerrer anahtarı `alıcı + kayıt + bant` üzerinden çalıştığı için
tek gider. Sets: *"tek bildirim olmalı çünkü kendisine gidiyor."* ⚠ İki kural iki olgu değildir — ikinci mail
birincinin aynısını söyler ve okuyan yalnız bizim ayar ekranımızda iki satır olduğunu öğrenir.

⚠ Bu davranış artık **testle sabit**: aynı kişiye iki kural, iki satır (mail + panel) — iki kural yüzünden değil,
iki **kanal** yüzünden.

#### İlk tarama: sertifika (01.08.2026)

`notifications:certificates-expiring` — orkestratör komutu. Kuralları okur, her bandı **tam o gün** dolan
sertifikalarla eşler, muhatapları çözer ve **iş atar**; hiçbir şey göndermez. Zamanlama ayardaki saatten okunur
(`withoutOverlapping` ayrıca konuldu: orkestratörün kilidi aynı pencereyi *iki kez atamayı*, bu ise yavaş bir koşuyu
kendi üstüne başlatmayı engeller — iki farklı hata, iki farklı kilit).

- ⛔ **Bant "tam o gün", "içinde" değil.** `<=` olsaydı her gün buradan bandın ucuna kadar her sertifika taranırdı;
  mükerrer anahtarı gönderimi engellerdi ama tarama boşuna tüm tabloyu dolaşırdı.
- ⛔ **Tarama firma firma yürür.** `certificate_records` satır güvenlikli: bağlamsız sorgu reddedilir, tek firmanın
  bağlamındaki sorgu ise o firmanın sertifikalarını bulup "tarama bu" der. Firma listesi **muaf** bir tablodan
  (`training_customers`) okunur — `customers` da tenant-scoped olduğu için sistem taramasının tavuk-yumurta sorunu
  orada çözülür.
- ⛔ **Müşteri izinleri aile bağlanmadan okunuyordu** *(ölçüldü — testin bulduğu ikinci hata)*. Çıplak bir
  `whereHas('permissions')` o an bağlı olan aileye sorar; yanlış aile kendinden emin bir "kimse yok" cevabı verir,
  ki burada **sessizce hiç kimseye gitmeyen bir uyarı** demektir (§6.1.1).
- ⛔ **Kanal seçimi olayın kitlesinden alınmıştı ve yanlıştı** *(ölçüldü — taramanın kendi testi buldu)*. Kural aynı
  olayı başka bir muhataba yöneltebildiği anda "kitle" olayın özelliği olmaktan çıktı: sertifika uyarısı katılımcı
  olayı olarak çizilmiş ama ticari olarak önemli kural onu **satışçıya** gönderiyor — ve satışçı panelinde satır
  alamıyordu. Artık her olay iki kanalı da kullanır.

---

### Bildirim: orkestratör / worker sözleşmesi (Sets, 01.08.2026)

⛔ **Worker aptaldır.** Kendisine **tek** iş verilir; yapar ya da yapamaz, sonucu yazar. Kendi işini seçmez, başka
iş aramaz, sırada ne olacağına karar vermez, verilen satırdan başka satıra dokunmaz. **Orkestratör** işi dağıtır ve
sonucu takip eder — işi veren ve teslim alan **aynı noktadır**.

⚠ Sebep bu ekibin daha önce yaşadığı somut hata: kendi işini seçen worker (`where status='queued' limit 10`),
**aynı on satırı seçen iki worker** demektir — ve aynı kişiye aynı şey iki kez söylenir. Bunu kuyruk ayarı çözmez;
yalnız o şekli yazmamak çözer.

Uygulanan:

- **Teslimat satırının kendisi iş kalemidir.** `Notify` **atar**, göndermez: kanal başına bir satır yazar ve id'yi
  worker'a verir. Panel bildiriminin worker'ı yoktur — satır zaten bildirimin kendisidir.
- ⛔ **Claim koşullu UPDATE'tir.** Kuyruk **en az bir kez** teslim eder: timeout sonrası retry, mükerrer tick,
  gönderip yazamadan ölen worker. İşin ilk yaptığı şey satırı "hâlâ sahipsizse al" güncellemesidir; ikinci kopya
  alacak bir şey bulamaz. Bu olmadan kuyruğun *en az bir kez*'i **müşteriye en az bir kez**'e dönüşür.
- ⛔ **Mail worker'ın İÇİNDE senkron gider** (`Mail::send`, `queue` değil). İşin kendisi zaten kuyruk; içeriden
  tekrar kuyruğa atmak, worker'ın sonucunu göremediği ikinci bir sıçrama demektir ve satır "gönderildi" derken mail
  hâlâ bir plandır.
- ⛔ **Mükerrer gönderim `dedupe_key` + kısmi unique index ile engellenir.** İki worker `exists()` kontrolünü
  birlikte geçer; ikincisini yalnız **veritabanı** reddedebilir. ⚠ Laravel'in `ShouldBeUnique`'i burada yanlış yer:
  o bir Redis kilidi — süreli, Redis uçarsa uçar, ertesi gün koşan tarama için zaten yoktur. Kalıcı gerçek kayıttır.
- ⛔ **Anahtar günü değil AŞAMAYI taşır** *(ölçüldü, 01.08.2026 — ilk yazımda hataydı)*. İlk hâlde anahtar
  `run_for` (koşu günü) taşıyordu; "30 gün içinde süresi dolanlar" taraması her gün koştuğu için her gün farklı
  anahtar üretiyordu — **aynı kişiye aynı sertifika için 30 gün boyunca her gün mail**. Şimdi anahtar `90` / `30` /
  `7` bandını taşıyor: uyarı, tarama kaç kez koşarsa koşsun tam üç kez gidiyor. ⚠ `band` **zorunlu parametre**:
  "aynı bildirim" ne demek, taramaya ait bir karardır ve varsayılan tam olarak bu hatayı sessizce yapardı.
- ⛔ **Unique ihlali NESTED transaction (savepoint) içinde yakalanır.** Ölçüldü (01.08.2026): savepoint olmadan
  yakalanan ihlal tüm transaction'ı abort eder — yani mükerrer bir bildirim, hakkında olduğu **teklifi geri alırdı**.
- ⛔ **Tarama sayaçları TÜRETİLİR**, worker'lar artırmaz. Her worker'ın artırdığı sayaç, iki worker'ın aynı anda
  artırdığı sayaçtır.
- ⚠ **Bir tarama, bir pencere, bir koşu:** `(sweep, run_for)` tekil. İki tick — iki sunucu, restart, elle tekrar —
  aynı işi iki kez dağıtamaz. Ret hata değil, **mekanizmanın çalışması**dır.
- ⚠ Horizon kuyrukları: `notifications` (tepkisel, önde) · `default` · `notifications-bulk` (taramalar).
  5.000 satırlık sertifika taraması, onay bekleyen teklifin mailini geciktirmemeli.
- ⚠ **Aynı yapı mail ve diğer iletişim alanlarında da kurulacak** (Sets) — yazışma gönderimi bugün doğrudan
  gidiyor, aynı orkestratör/worker hattına alınacak.

#### Saklama: SÜRESİZ (Sets, 01.08.2026)

⛔ `notification_deliveries` **hypertable** (30 günlük chunk, `created_at` üzerinden) ve **retention politikası yok**.
Sebep Sets'in kendi cümlesi: *10 yıla kadar uzayabilen sorumluluklar var ve bir kısmı kullanıcının tetiklediği
aksiyonlar.* "Bu kişiye söylendi mi, hangi metinle, hangi dilde, gitti mi" sorusu 2026'da verilen bir eğitim için
2034'te sorulabilir; cevabı yalnız bu satırda. ⚠ Üründe zamanlı veri silen tek tablo `audit_usage`'dır ve orada
serbest olmasının sebebi **kanıt olmamasıdır**; burası kanıt.

⛔ **Bu tablo bir worker logu değil, iş kaleminin kendisidir** — worker'ın claim ettiği satır, mükerrer gönderimi
engelleyen anahtarın sahibi, orkestratörün ilerlemeyi saydığı yer. Ayrıca **paneldeki bildirim listesi bu
satırlardır**; silmek kullanıcının listesini silmek olur. Audit bunu karşılamaz: audit "kim neyi değiştirdi"
sorusunu cevaplar, bildirimi yazan worker'ın **aktörü yoktur**.

⛔ **Bölümlemenin bedeli ödendi:** TimescaleDB (ve PostgreSQL'in kendi bölümlemesi) **her unique index'te bölümleme
kolonunu ister**. `(dedupe_key, created_at)` üzerinde tekillik, aynı bildirimin **bir saniye arayla iki kez**
yazılmasına izin verirdi — yani anahtarın var olma sebebini yok ederdi. Bu yüzden tekillik **`notification_dedupe`**
adlı dar ve bölümlenmemiş bir tabloya taşındı: teslimat satırı *ne olduğunu*, dedupe tablosu *atanıp atanmadığını*
söyler. İki tablo ama tek soruya iki cevap değil.

⚠ **Chunk yalnız ZAMANA göre.** `channel` üzerinde space dimension iki değerden iki bölüm çıkarır ve chunk sayısını
boşuna katlar; Timescale'in space dimension'ı düğümlere yaymak içindir. "Şu kanala bakayım" bir **indeks**
sorusudur ve indeksi kondu — Postgres dışladığı satırları zaten hiç okumaz, aynı chunk'ta olsalar da.

---

### Bildirim altyapısı — çekirdek (EGT-37, 01.08.2026)

⛔ **Her şey tek action'dan geçer** (`Notify`). Beş şey her mesajda olmak zorunda ve her biri bir kez unutulan
cinsten: tercih dinlenir, gönderim dışı listesine bakılır, yöneticinin metni **yayınlandığı sürümüyle** kullanılır,
alıcının kendi dili seçilir, gönderim **kaydedilir**. Otuz bildirim sınıfı, bunlardan birini unutmak için otuz yer
demektir — ve hata sessizdir: hiç gönderilmemiş mesajla cevaplanmamış mesaj aynı görünür.

- ⛔ **Notify asla exception fırlatmaz.** Bildirim, olmuş bir şeyin *sonucudur*: teklif onaylandı, onay gerçektir,
  haber vermek nezakettir. Buradan çıkacak bir exception, birinin posta kutusu dolu diye **onayı geri alırdı**.
  Her ret, gerekçesiyle bir gönderim satırına dönüşür.
- ⚠ **Satır senkron yazılır, yalnız mail kuyruğa gider.** Tümüyle kuyruğa atılan bir gönderici, worker öldüğünde
  kaydı da kaybeder.
- ⛔ **Panel bildirimi ile mail AYNI tabloda.** İki tablo, "bu kişiye söylendi mi" sorusuna iki cevap demektir;
  ayrıştıkları gün ekrandaki yanlış olandır.
- ⛔ **Zorunlu bildirim tercihi tanımaz** ve çıta "önemli mi" değil: *hak kaybı ya da yasal süre kaçırma*.
  Onay bekleyen teklif, bakanlık son günü, altından iptal edilen eğitim, süresi dolan sertifika.
- ⛔ **Yayınlanmış metni yoksa hiçbir şey uydurulmaz** — anahtar + payload'dan kurulan bir mail stack trace gibi
  okunur ve metni yazmamış yönetici bunu hiç öğrenmez. Ret, gerekçesiyle kaydedilir.
- ⛔ **Şablon Blade değil, yerine koyma.** Metin veritabanından geliyor ve yönetici yazıyor; Blade olarak derlemek,
  hakkında güvenlik bülteni yazılan şeklin ta kendisi. Bilinmeyen yer tutucu **görünür bırakılır** — `{{ dfeadline }}`
  bir dakikada düzeltilir, sessizce boş kalan bir yıl boyunca "lütfen . tarihine kadar dönüş yapın" gönderir.
- ⚠ **Gönderim dışı listesi ADRESE aittir**, kişiye değil: adresi düzeltilen kişi eskisinin cezasını çekmez.
  Yalnız **sert** dönüşler kapatır — dolu posta kutusu bir haftalık tatil yüzünden gerçek alıcıyı kaybettirirdi.
  Kaldırma silinerek değil, **kayıtla** yapılır; yeni bir dönüş kaldırmayı geçersiz kılar.

⛔ **Şema hatası bulundu ve düzeltildi:** `notification_templates` `(key, version)` üzerinde tekildi. Oysa bir
bildirimin **iki metni** var (mail + panel) ve ikisi kasten farklı yazılır. Eski indeks altında bir olayın mail v1'i
ile panel v1'i çakışıyordu — yani bir olay ya birinde ya ötekinde metne sahip olabiliyor, ikisinde birden asla.
İki ay fark edilmemişti çünkü **tablonun hiç göndericisi yoktu**. Yeni tekillik: `(key, channel, version)`.

⚠ Olay kataloğu (`NotificationEvent`): satış, fatura, planlama, eğitmen, katılımcı, içerik paketi ve ofis işleri —
27 olay. Promosyonel hiçbiri yok; `isPromotional()` var ki ilk promosyonel olay **birisi karar vermeden** eklenemesin
(AD-17/6563: ticari ileti onayı + broadcast stream).

⚠ **Kalanlar:** panel bildirim listesi/zil ekranı, tercih ekranı, olayların mevcut action'lara bağlanması,
şablonların Filament tarafı. Çekirdek ve testleri hazır.

---

### Çalışma sırası — ofis önce, müşteri sonra, girift alanlar en son (Sets, 01.08.2026)

⛔ **Bazı çakışmaları çözme şansımız yok** ve bu kabul edilmiş bir gerçektir: müşteri tarafı bitmeden teklif ve
eğitim tamamlanamıyor, teklif/planlama bitmeden müşteri tamamlanamıyor. Sıra bu yüzden şöyle:

1. **Bildirim altyapısı** — çapraz bağımlılık, hepsinden önce.
2. **Eğitmenin tüm ekranları** (`40`, `41` + hakediş zinciri).
3. **Planlamacının tüm ekranları** (Gündem tasarımcıda).
4. **Satışçının tüm ekranları** (`30`, `32`, `33`).
5. **Müşteri paneli — katılımcı dahil** (`50` müşteri turunun içindedir, ayrı tur değildir).
6. **Girift alanlar** — iç içe geçmiş yerler en son, nakış gibi.
7. **Mali taraf en sonda** (`62`'nin finansal panosu, "finansal tabloları görebilir/göremez" yetkisi).

⚠ **"Bildirimleri eksiksiz tamamla" tam olarak mümkün değil ve bu kabul edildi:** bildirim altyapısı bir olay
kataloğu ister, bazı olaylar ancak o modülün ekranı yazılınca netleşir. Uygulanan: **çekirdek + bugün kesin olan
olaylar**; ana yapı hazır, çıktıkça yalnız yeni bildirim eklenir.

⚠ **Tasarım borcu — `63` sistem logu.** Tasarımcının hazırladığı tasarım kullanılmamış (kullanıcı detayında
"Bu haftası", "Performans (Q3)", "Sonuçlananlar — bugün", "Kural özeti" blokları çizili, kodda yok). Backlog.

---

### Eğitim anketi — motor kararı (Sets, 01.08.2026)

Backoffice'te **Eğitim** altında ayrı bölüm: basit anket motoru — **evet/hayır · 1–5 puan · serbest metin**.
Eğitim tanımına varsayılan anket atanır, tek bir eğitime **ek soru** eklenebilir. Eğitim tamamlandığında
katılımcılara gider. ⛔ Sonuçlar **yalnız yöneticide**.

Üç kural baştan:

- ⛔ **Anket sürümlenir, yerinde düzenlenmez.** Soru metni değişince eski cevaplar eski soruya ait kalmalı; aksi
  halde "geçen yılki ortalama" aynı sayı ama başka bir sorunun cevabı olur.
- ⛔ **İsimsizlik gerçek olacak: davet ve cevap AYRI tablolarda, aralarında bağ yok.** Kimin doldurmadığını bilmek
  gerekiyor (hatırlatma için), cevabı kişiye bağlamamak gerekiyor. Tek tabloda `enrolment_id` + cevap tutmak,
  ekranda "isimsiz" yazan ama veritabanında isimli olan bir anket üretir.
- ⚠ **Asgari cevap eşiği** (yönetici tanımlı, öneri 3): üç kişilik eğitimde tek yorum kimin yazdığını ele verir.

⚠ Yeni katılımcı ekranı çizimine gerek yok — katalogda segmented, yıldız ve textarea var; `50` zaten çizili.
Çizilecek tek şey **yöneticinin anket sonuç ekranı**. Brief: `docs/design/egitim-anketi-brief.md`.
**Henüz yazılmadı.**

---

### İçerik paketleri ürünleşti + iki tahsilat kararı (Sets, 01.08.2026)

**Dekont zorunlu DEĞİL.** Tasarım `62` zorunlu tutuyordu; Sets'in duruşu: *"doğrusu zorlamamak, duruşumuz belli
yerler hariç hep bu olacak, sistemi kilitlemeyecek şekilde belirgin hatırlatmalar."* Uygulanan: dekont boşken
girişte belirgin uyarı, listede "dekont yok" işareti, fatura altında ":count tahsilatın dekontu yok" satırı —
**hiçbiri kaydı engellemiyor.** Zorlamak, dürüst kaydı yavaş kaydı yapar; yavaş kayıt kimsenin tutmadığı kayıttır.

**Banka/hesap alanı yok.** Tasarımda vardı; ödemeler banka sisteminden takip edilmiyor, dekont yüklenirse
dekonttan takip ediliyor. Alan açılmadı.

**İçerik paketleri artık ürün.** Eklenenler: ürün tipi, vitrin özeti, baskı/sürüm, lisans birimi (firma/koltuk),
koltuk sayısı, indirilebilirlik, filigran, yeni baskının dahil olup olmadığı, **liste fiyatı** ve
**telif/lisans maliyeti**; doküman düzeyinde indirilebilirlik/filigran geçersiz kılma ve **örnek** işareti.

- ⛔ **Tip bir RAF ETİKETİ, davranış ayrı anahtarlarda.** Her şeyi tipe bağlayan enum ilk gerçek üründe kırılır:
  o ürün *el kitabı + içindeki Excel çizelgeleri*dir ve parçaları farklı davranır. Şablonda filigran kapatılır —
  doldurulacak formun üstüne filigran basmak koruma değil, tahribattır.
- ⛔ **Maliyet nullable, sıfır değil.** Maliyeti girilmemiş paket, maliyeti olmayan paket değildir; `0` varsayılanı
  ikisini ayırt edilemez yapar ve teklif marjını olduğundan iyi gösterir.
- ⚠ Tipler: el kitabı · doküman seti · şablon/araç seti · takip aboneliği · kayıt (video/ses) · soru bankası.
  **Kayıt tipi kodda yok** — depolama ve oynatıcı ayrı bir iş; tip listede duruyor ki sonradan şema değişmesin.

Tasarımcı brief'i: `docs/design/icerik-paketleri-brief.md` — satışçı katalog ekranı (yeni), müşteri vitrini (yeni),
`16`'nın eksik hâlleri; **6 açık soru** (katalogdan teklife ekleme, vitrinde fiyat, uygulama içi okuyucu, koltuk
lisansı zorlanacak mı, video, yeni baskıda eskisine ne olur).

---

### Tasarımcı turu 1 uygulandı — `14`, `16`, `31`, `62` (01.08.2026)

Tasarımcı dört ekranı revize etti, dördü de koda geçti.

**`62` Muhasebe — fatura detayı ve tahsilat.**
- ⛔ **Tahsilat silinmez; ters kayıtla iptal edilir ve ikisi de görünür kalır.** Negatif tutar yerine
  `reverses_payment_id`: negatif satır **iadeden** ayırt edilemez, iade ise farklı bir olaydır. `outstanding()`
  ters kayıtlı çiftin **ikisini birden** saymaz — birbirini götürsün demek, aynı sayıyı yanlış sebeple üretmek olur
  ve hedefsiz bir ters kayıt toplamı sessizce düşürürdü. Ters kaydın tersi alınamaz.
- ⛔ **Durum hesaplanır, seçilmez:** Ödeme Bekliyor → Kısmen Tahsil Edildi → Ödendi. Elle seçilen durum, son taksit
  geldiği gün kimsenin taşımayı hatırlamadığı durumdur.
- ⚠ **Mahsup** yöntem listesine eklendi. `other` taşırdı ama sorun da tam o: mahsup "sıra dışı bir havale" değil,
  hiç hareket etmemiş paradır.

**`16` Müşteri.** Faturada **KALAN** kolonu, kısmi tahsilat çubuğu ve **tahsilat dökümü** (tarih · tutar · yöntem).
⚠ Yöntemi göstermek önceki kararın düzeltmesi: müşterinin **nasıl ödediği müşterinin kendi bilgisidir**; "₺30.000
tahsil edildi" deyip ne zaman olduğunu söylememek tam da ekranın önlemesi gereken telefonu ürettiriyordu. İçeride
kalan: dekont, not ve kaydı giren. ⛔ Ters kayıtlı tahsilat müşteri dökümünde **hiç görünmez** — iki kendi
satırımız arasındaki düzeltme bizim muhasebemizdir, onların borcu değil.

**`31` Satışçı teklif.** Kalem tablosu **planlı eğitime göre gruplu**; her grubun başlığında kontenjan, altında
"＋ Bu eğitime kalem" — modal o eğitim bağlamıyla açılır, eğitim seçtirmez. Paketler **kendi grubunda ve en sonda**:
kontenjanı ve tarihi yok, eğitim satırlarıyla aynı tabloda iki kolon boş kalırdı.

**`14` Müşteri teklif isteği.** Sayı zorunlu, **isimler opsiyonel öneri**. `training_request_participants`
(tenant-scoped) — yalnız firmanın kendi personeli; yabancı id sessizce düşer, çünkü "başka firmanın çalışanını
ekle"nin yazım hatası okuması yok. ⛔ Ekran **isim taşıdığını söyler** ve "İsimleri kaldır, yalnız sayı gönder"
düğmesi verir; sonradan öğrenen kişi haklı olarak kızar, önceden bilen kişi tercih yapmış olur.

---

### Tasarımcı turu 1 — kararlar (Sets, 01.08.2026)

Tasarımcının itiraz ve sorularına verilen cevaplar. Tam metin: `docs/design/tasarim-revizyon-1-yanit.md`.

- ⚠ **Teklif isteğinde isim OPSİYONEL, sayı ZORUNLU.** Seçilen kişiler talebe **öneri** olarak taşınır; kesin liste
  planlamadan sonra katılımcı listesi ekranında teyit edilir. Tasarımcının gerekçesi kabul edildi: sayıya inmek uyum
  kartlarını öldürüyor ve müşteriyi "kim eksik" sorusunu Excel'de yönetmeye geri gönderiyor. ⛔ **Henüz yazılmadı** —
  talep bugün yalnız sayı taşıyor. İsim eklendiğinde talep kişisel veri taşıyan bir kayıt olur: saklama süresi,
  erişim logu ve talebin iptali ayrıca ele alınacak; ekranda "bu talep şu kişilerin adını taşıyor" satırı olacak.
- ⚠ **Teklif PDF anteti backoffice'ten yüklenir.** Hazır kurumsal şablon yok; antet yönetici tarafından yüklenecek
  bir dosya olacak. ⛔ Henüz yazılmadı; antet yüklenmemişken PDF'in nasıl göründüğü ve antet değişince eski
  tekliflerin ne olacağı karar bekliyor.
- ⛔ **Anket sonuçları yalnız yöneticide** — puan da, yorum da. Eğitmen kendi ortalamasını **görmez**;
  `40 Egitmen Paneli`'nde puan alanı olmayacak. `22`'deki eğitmen kartındaki puan yönetici ekranı olduğu için
  kalabilir, ama **kaç cevaba dayandığı** yazmalı: iki cevaptan gelen 5,0 ile kırk cevaptan gelen 4,6 aynı şey
  değildir. ⚠ Yöneticinin anket ekranı tasarımda yok, isteniyor.
- ⚠ **Teklif kalem tablosu planlı eğitime göre gruplanacak** (tasarımcının önerisi, itiraz edilmedi): her grubun
  altında "＋ Bu eğitime kalem". `31` yazılmış durumda; bu düzene taşınacak.
- ⚠ **Sürüm karşılaştırmasında** kalem + tutar farkı ana kıyaslama, şart metni değişikliği ayrı rozet. İkisi farklı
  soru: biri "ne kadar değişti", diğeri "neyi kabul ediyorum".

---

### Rol ayrımı: yoklama eğitmenin işidir (Sets, 01.08.2026)

⛔ **Planlamacı sadece planlar, satışçı sadece satar, eğitmen sadece eğitimi verir.** Yoklamayı — kimin geldiğini,
kimin gelmediğini, imzalı listeyi — **eğitmen** saha ekranından (`41`) halleder. Sebep Sets'in kendi cümlesi:
imzayı OCR ile taratmak gerekiyor ve tutarsızlık çıkabilir, katılımcı mobilden check-in yapmak istemeyebilir;
odada olmayan birinin kimin orada olduğuna karar vermesi doğru değil.

Bunun kodda üç sonucu oldu:

- ⛔ `FinishTraining` **otomatik `no_show` işaretlemez** (yazılmıştı, geri alındı). Kapanmamış bir kişi varsa kilit
  **reddeder ve kişinin adını söyler** — bu ret, eğitmene hatırlatmanın kendisidir.
- ⛔ İmzalı liste yükleme kontrolü **`22`'den kaldırıldı**; ekran yalnız listenin gelip gelmediğini ve önizlemeyi
  gösterir. ⚠ `FileTrainingRegister` action'ı ve ofis ucu duruyor — `41` yazıldığında oradan çağrılacak.
- ⚠ Boş yoklama kâğıdı **yazdırılabilir sayfa** olarak üretiliyor (`/egitimler/{id}/yoklama-listesi`), PDF üretici
  ile değil: FPDF'in çekirdek yazı tipleri Türkçe harfleri çizemiyor (filigranda ölçüldü) ve adları bozuk çıkan bir
  yoklama kâğıdı, imzanın yanına konan kâğıt olduğu için yokluktan kötüdür.

### Eğitmen ve eğitim puanlaması — yeni özellik (Sets, 01.08.2026)

⚠ **Eğitime katılanlar eğitimin sonunda eğitimi ve eğitmeni puanlayacak.** Anket tasarım `50`'de zaten çizili:
2 dakika, **isimsiz**, ★ + serbest yorum kutusu. Kodda **hiç yok** — veri, kural, ekran, hiçbiri yazılmadı.

⛔ **Sonuçlar yalnız yöneticiye gösterilir** *(Sets, 01.08.2026)* — puan da, yorum da. Eğitmen kendi ortalamasını
görmez.

---

### Planlama takvimi (tasarım `23`, 01.08.2026)

`/planlama/takvim` — **satırlar kişi, kolonlar gün**. Planlamacı bu ekranı "28'inde kim boş" diye açar,
"ağustosta ne var" diye değil; eğitim başına satır ikinci soruyu cevaplar ve birinciyi göz kararına bırakır —
birinin zaten verdiği eğitimin teklifini yine ona göndermenin yolu tam olarak budur.

- ⚠ **Çakışma hesaplanır, saklanmaz.** Atama kuralları çakışma yaratmayı reddeder; bu ekrandaki çakışma
  **tarihin sonradan kayması**yla oluşur. Bayrak olarak saklamak, atamalarla senkron tutulacak ikinci bir şey
  demektir ve ayrıştıkları gün ekranda boş görünen kişi boş olmayan kişidir.
- ⚠ **Yalnız `confirmed`/`reconfirming` günü doldurur.** Cevaplanmamış teklif meşguliyet değildir.
- ⛔ **İzin sebebi hiç çizilmez** (Sets, 30.07.2026 kuralının devamı): planlamacıya günün kapalı olduğu söylenir,
  neden kapalı olduğu değil.
- ⚠ Görünüm ve gün **query parametresi**dir (`?gorunum=hafta&gun=…`) — "şu hafta"nın bağlantısı paylaşılabilsin
  diye. Ay/Hafta/Gün üçü de çalışır; ay 31 kolonla yatay kayar, sıkıştırılmaz.
- ⚠ Boş hücredeki ＋ o günle planlama sayfasını açar. **Eğitmeni taşımaz**: atamanın kendi kapısı var (yeterlilik,
  belge, çakışma) ve takvimden kişi seçtirmek ya o kapıyı atlar ya da açıldığı şeyi reddeden bir form üretir.

---

### Eğitim detayı tasarıma getirildi — tamamlanma kilidi (tasarım `22`, 01.08.2026)

Planlamacının eğitim detayı `design/22`'ye göre yeniden yazıldı: statü şeridi, satır içi KUMS bandı, üç kart,
katılımcı tablosu, marj, geçmiş ve **yoklama yarısı**. Kapsama matrisi: `docs/reviews/tasarim-22-kapsama.md`
(6 satır açık, karar bekliyor).

Bu turda alınan kararlar:

- ⛔ **İmzalı yoklama listesi kendi tablosunda ve tenant'sız** (`training_registers`). `training_completions`
  içindeki `signed_register_document_id` genel `documents` tablosunu gösteriyordu; o tablo `documents_per_owner`
  ile `customer_documents`'e çevrilmiş ve polimorfik sahibi düşürülmüştü — yani kolon **hiçbir şeyi göstermiyordu**
  ve tamamlanma kilidi hiç açılamazdı. Bir odada **tek** kâğıt dolaşır ve açık eğitimin odasında birden çok firmanın
  personeli oturur; kâğıdı firma başına dosyalamak ya A firmasının isimlerini B'ye gösterir ya da tek nüshası olan
  bir evrakın kopyasını çoğaltır. Kâğıt 3E'nin kendi kanıtıdır; **hiçbir müşteri sorgusu bu tabloya dokunmaz.**
  Silme veritabanı seviyesinde reddedilir.
- ⛔ **Eğitimin tamamlanması ile kişinin tamamlanması iki ayrı iştir.** `CompleteTraining` bir kaydı kapatır
  (dört unsur kişi başınadır); `FinishTraining` eğitimin tamamını kapatır — **hepsi ya da hiçbiri**. Yarısı
  tamamlanmış bir eğitim, kolay kişilerin sertifikasını basıp zor olanı kimsenin bakmadığı bir durumda bırakır.
  Ret mesajı **kişinin adını** taşır.
- ⚠ **Ne imzası ne beyanı olan kişi `no_show` işaretlenir, kilidi bloklamaz** *(uygulandı — Sets onayı bekliyor)*.
  Tasarım `22` bu satırı "Katılmadı" olarak çiziyor ve sonucunu yazıyor: katılmayan sertifika alamaz. Kapıdan
  geçirmek, hiç var olmayacak bir imza yüzünden tüm eğitimi kilitler ve planlamacıya tek çıkış olarak imza
  uydurmayı bırakır. **İki kayıt da yokken** geçerlidir; kayıtlar çelişiyorsa o bir çelişkidir ve gerekçesiyle
  **yazılarak** çözülür.
- ⚠ **Marj planlamacıya gösterilir** (`TrainingRevenue`): yalnız **onaylı** teklifler sayılır — gönderilmiş teklif
  gelir değil, umuttur. Teklif geneli indirimi kaleme değil teklife uygulandığı için eğitimin payı, kalemlerinin
  netinin teklifin gerçek toplamına oranlanmasıyla bulunur.

---

### "Kokpit" → **Gündem** (Sets, 01.08.2026)

⛔ Personelin işini yönettiği ekranın adı **Gündem**, sayfa başlığında **Gündemim**. Sebep: kokpit tek kişinin bir
aracı **kullandığı** yerdir; bu ekran kişinin **işini takip ettiği** yerdir. Planlamacı, satışçı, operasyon ve
eğitmende **tek kelime** kullanılır — ekranın cevapladığı soru dördünde de aynı: *bugün ne var, ne bekliyor, ne
gecikti*. ⚠ `Görevlerim` ile çakışmaz: görevler bir liste, gündem bakışın kendisi.

⚠ Sözlük ANAHTARI `cockpit` olarak kaldı; değiştirmek onu okuyan her ekrana kullanıcının hiç görmediği bir dize için
dokunmak olurdu.

---

### Fiyat listesi sürümlenir, kapanmaz (EGT-74, 06.08.2026)

⛔ **Liste tektir, sürüm kazanır** *(Sets)*. Tür başına tek liste; her satır numaralı bir sürüm. *"Yeni sürüm oluştur"* mevcut sürümü **taslağa kopyalar** (hangi hücrenin elle girildiği bilgisi dahil), *"Yayınla"* onu yayına alır. Eski sürüm **arşive düşer, silinmez, kapanmaz**.

⛔ **Otomatik kapatma fikri denendi ve reddedildi.** Sets: *"o anda aktif teklif vardır, aktif eğitim vardır, bekleyen fatura vardır — ilişkisel olarak devam etmek zorunda."* Ölçüldü: kapatmaya **gerek de yok** — tutar kaleme kopyalanıyor, kalem sürümü kaydediyor, arama zaten *"en yeni yayımlanmış ve başlamış"* diyor. `effective_to` hiç yazılmıyor.

⛔ **Bir sürüm, kendisinden TEKLİF KESİLDİĞİNDE kapanır** — yayımlandığında değil *(Sets)*. Kuralın koruduğu delildir; hiçbir kalemin işaret etmediği sürümde delil yoktur. Tek yönlü kapı.

⚠ Ekran üç durum gösterir — **Taslak · Yayında · Arşiv** — ve üçü de satırlardan **türetilir**, saklanmaz. Boş sürüm yayımlanamaz: yayımlansa güncel liste olur ve her teklif tutarsız kalır.

⛔ **Matris gerçek tablo oldu**: dikeyde süreler, yatayda müşteri tipleri, sütun başlığında grubun yüzdesi. Elle girilen hücre çerçeveyle ayrılır (eskiden her kutunun altındaki cümle kolonları bozuyordu).

⛔ **Düzenleme ekranı hiçbir zaman kaydetmiyormuş.** Alanlar `data.matrix`'e bağlıydı, kaydetme `$this->matrix`'i okuyordu; Kaydet eski sayıları geri yazıp *"kaydedildi"* diyordu. Bugüne kadarki testlerin hepsi **eylemi** sürüyordu ve eylem baştan beri doğruydu. `docs/specs/fiyatlama-spec.md §5.1` yazıldı.

---

### Test izolasyonu varsayılan oldu (EGT-75, 06.08.2026)

⛔ `RefreshDatabase` **dosya başına opt-in**'di ve satır yazan **207 dosyadan 99'u** almamıştı. Artık temel sınıfın seçildiği yerde veriliyor (`tests/Pest.php`), yani yeni bir grup izolasyonsuz eklenemez. RLS grubu kendi sınıfını korur (truncate; `SET LOCAL` saran transaction'ın dışında anlam taşımaz).

⛔ **CI artık `migrate:fresh` koşuyor.** Migration'lar prod öncesi katlandığı için kalıcı bir veritabanı katlanan değişiklikleri **asla** alamıyordu — build #908 böyle düştü. ⚠ Adıma **hedef koruması** kondu: hedef veritabanı `_test` ile bitmiyorsa betik durur (04.08'de elle koşulan `migrate:fresh --seed` Sets'in hesabını böyle götürmüştü).

**İzi de temizlendi — ticket kapandı (09.08.2026).** İş 06.08'de bitmişti ama **ölmüş gerekçe 13 dosyada yazılı kalmıştı**: *"suite testler arasında veritabanını temizlemiyor"*. Etrafındaki kod bu varsayıma göre kurulmuştu — her çağrıda `uniqid()`'li isimler, sıfırla karşılaştırmak yerine önce/sonra sayan assertion'lar. Zarar vermiyorlardı ama **bir sonraki kişiyi yanıltırlardı**.

⛔ Bir tanesi testi gerçekten zayıflatıyordu: `StaffQueryTest`, *"bu çağrı eğitim açmaz"* iddiasını `count() === $trainingsBefore` diye yazıyordu — yani **eğitim açan bir çağrı için de geçerdi**, yeter ki ondan önce başka bir eğitim yaratılmış olsun. Artık `toBe(0)`.

⚠ Üç yer **bilerek duruyor**: `AdministrativeDivisionsTest`'in ikisi zaten *"06.08.2026'da bu doğru olmaktan çıktı"* diye yazıyor (tarihçe), `CustomerDoorThrottleTest`'inki ise **hâlâ doğru** — limitleyici önbellekte yaşar, `RefreshDatabase` onu temizlemez.

ℹ Ölçüldü, varsayılmadı: `audit_usage` **hypertable'ı da** testler arasında boş geliyor — "hypertable sıradanı gibi boşalmaz" gerekçesi de yanlıştı.

---

### Geliştirme kimliği: dev@subicon.net (06.08.2026)

⛔ **Passkey ödünç alınamaz** — özel yarısı cihazdan çıkmaz. `sets@subicon.net`'e parola eklemek sahibine istemediği bir giriş yolu açardı, o yüzden **ayrı kimlik**: üç panelde de tam yetkili, yazılı TOTP. `passkey:remember` Sets'in passkey'ini `migrate:fresh` sonrası geri yükler (dosya depoda değil; açık anahtar gizli değildir ama gerçek bir kişinin cihazını adlandırır).

---

### Bildirim ayarları ve sidebar (06.08.2026)

⛔ **Kart = olay** *(Sets)*. 32 olayın hepsi çizilir; kuralı olmayan olay da görünür ("kimse uyarılmıyor"). Kural satırı tek satırdır; **yeşil = çalışır, kırmızı = çalışmaz** (üç sessiz durum: kapalı, süresiz, grupsuz). Süreler **kapalı liste** — değerler `option_lists`'te, yani yeni bir süre satırdır, deploy değil.

⛔ Arama + Kaydet birlikte **eşleşmeyen kuralları siliyordu** (Filament gizli bileşenin state'ini düşürür, kurallar küme olarak değişir). `dehydratedWhenHidden` ile kapandı.

⚠ Sidebar: gruplar arası 28px → 8px + ayraç çizgi; **Kullanıcılar** Panel'in hemen altına alındı.

⛔ **Filament varlık sürümleme hatası**: proje CSS'i değişince URL değişmiyordu (`?v=` paket sürümünden geliyor), tarayıcı eskisini servis ediyordu. Panel artık kendi varlıklarını dosyaların durumundan damgalıyor.

---

### Bildirim yüzeyi ve migration konsolidasyonu (EGT-74, 06.08.2026)

**Bildirim yüzeyi zil değil, sidebar kalemi** *(Sets, 05.08.2026)*. Topbar 28.07.2026'da bilinçli kaldırılmıştı; sayacı rail'e koymak iki navigasyon yerine bir navigasyon bırakıyor. Tasarım `05`'in zili katalog deseni olarak kalıyor, kabuk olmuyor. `RailCounts` bir `notifications` anahtarı üretiyor, bekleyenlerden biri kritikse rozet kırmızı; yeni bildirim gelince rozet yanıp sönüyor ve okuyucu ekranı açınca duruyor.

**Bildirim satırı kayda götürür** *(Sets, 06.08.2026)*. `notification_deliveries.subject_type`/`subject_id` chunk 7'den beri yazılıyordu ve hiç okunmuyordu. Artık okunuyor: politika adres yazılmadan sorulur, 403 verecek bağlantı hiç doğmaz. **Görev bir işaretçidir, varış noktası değil** — `tasks` da kendi konusunu taşır, bu yüzden görev bildirimi görevin bağlı olduğu eğitime/teklife gider; görev listesi yalnız ekranı olmayan kayıtlar için yedektir.

⛔ **Adres disiplini tekdüze değil.** Eğitim, müşteri ve eğitmen ekranları `OpaqueId` token'ı çözer; teklif ve fatura ham id ile bind edilir. `OpaqueId` anahtarını **oturumdan** türettiği için kuyruk worker'ında üretilen token kimse için çözülmez — toast bu yüzden kayda değil listeye götürür.

**Migration'lar geçmişi değil şemayı anlatır** *(Sets, 05.08.2026)*. 42 saf alter dosyası ana dosyalara katlandı (145 → 102). Kural: katlanan kolon **onu barındırabilecek en erken dosyaya** gider; birbirini iptal eden çift silinir; yeniden adlandırma, tabloyu nihai adıyla kurarak katlanır. Production'a çıkıldıktan sonra yeniden alter migration'la ilerlenecek.

**Ticket'lar:** EGT-76 (D-6 İYS/SMS), EGT-77 (D-13 kurum yetki belgesi), EGT-78 (D-15 muafiyet motoru), EGT-79 (D-16 IMDG ön koşulu). D-15/D-16 kapsamı koddan doğrulanarak daraltıldı — eğitim alanı seçenek listesi, belge türü bayrağı ve diploma kaydı zaten var; eksik olan **muafiyet kuralının kendisi** ve **zorunluluk kavramı**.

### Muafiyet motoru kapandı (EGT-78, 09.08.2026)

⛔ **Zemin doluydu ama ortada buluşmuyordu.** Seçenek listesi, `document_types.has_education_field` anahtarı ve `customer_documents.education_field_option_id` yıllardır oradaydı; ölçüldüğünde üç kopukluk çıktı: (1) kolon **dört belge tablosundan yalnız birinde** vardı — mevzuatın asıl sorduğu eğitmen ve katılımcı diplomaları bölümünü kaydedemiyordu; (2) **hiçbir şey o kolona yazmıyordu**, yani anahtar hiçbir yere bağlı değildi ve canlıda dolu tek satır yoktu; (3) kuralın kendisi yoktu.

⛔ **Motorun eksik katmanı belgenin İÇİYDİ.** `InstructorCompetence` *"kabul edilen bir belge TÜRÜ var mı"* diye soruyordu; DG-2020 md.14/6 ise *mühendislik fakültesi* mezuniyeti istiyor. Artık ön şart bir alan kümesi adlandırabiliyor — ⚠ **boş bırakmak ANY demek**, yoksa on iki seedli ön şartın on biri bir gecede binadaki her eğitmeni reddederdi.

⛔ **Muafiyet eğitime bağlanır, belgeye değil.** Madde kişiyi bir EĞİTİMDEN muaf tutar (*"birinci fıkradaki eğitim şartı aranmaz"*), o yüzden `participant_exemptions` eğitim tanımından sarkar. ⚠ **Uyarıdır, kapı değildir** (AD-27): metin "aranmaz" diyor, "katılamaz" demiyor — reddeden bir sistem mevzuatta olmayan bir yasak icat ederdi.

⚠ Kapalı küme **koda gömülmedi** (kırmızı çizgi 9 ve 14): dokuz DGY eğitimi × on program = 90 satır seed'den gelir, sonrasında 3E'nindir. Madde değişirse cevap bir düzenlemedir, bir deploy değil.

⚠ Mevzuat metni PDF'ten birebir doğrulandı; mevcut ağaçta **ön lisans ve lise katmanı hiç yoktu**, ikisi de eklendi.

---

### Chunk 4 uygulandı — teklif, indirim limiti, fatura (EGT-74, 01.08.2026)

Sets: *"devam edebilirsin, eksiksiz tamamla."* Uygulandı; alınan ve **ölçülen** kararlar:

| Karar | Özü |
|---|---|
| **`expired` statü DEĞİL** | Geçerlilik geçmesi `valid_until < now()` ile türetilir, uyarı üretir; kontenjan `held` kalır. Kapatan tek şey `CancelQuote` — insan, gerekçeyle. Zamanlanmış komut yalnız **hatırlatma** gönderir (`quotes:send-validity-reminders`), statüye dokunmaz |
| **Fiyat dondurulur** | `unit_price` listeden kopyalanır, `price_list_version` geldiği listeyi söyler; yeni liste yayınlamak çıkmış teklifi değiştirmez. KDV oranı da teklife yazılır |
| **`price_kind` = `daily` \| `per_participant`** | ⚠ Plan `per_person` yazıyordu; fiyat listelerinin kendi sözlüğü (`PriceKind`) `per_participant`. Tek kavrama iki kelime, kalemin yanlış listeden fiyatlanma yoludur — hizalandı |
| **Onay hash'e bağlıdır** | Yönetici onayı **o metne** verilir; satışçı sonradan indirimi büyütürse hash tutmaz ve teklif tekrar onaya döner. Geçerlilik tarihi metnin içinde olduğu için onaya gönderirken yazılır |
| **Kontenjan uyarısı metnin içinde** | M-37: rozet değil gövde, dolayısıyla hash'in içinde. Yer yoksa metinde **"YEDEK LİSTE"** ibaresi geçer (M-33) |
| **EGT-58 iki yarısıyla kapandı** | (a) global üstü limit **onaya düşer, onaysız etkin olmaz** (`user_discount_limits.pending_percentage`); (b) etkin tavanı aşan teklif onaya düşer ve aşım ekranda belirgin. Onaylı geniş limitin **içindeki** teklif normal akıştan gider |
| **`ApprovalKind::DiscountLimit` dolduruldu** | Sıfırla duran beşinci sekme artık gerçek: `discount_limit.approve` izni tanımlandı, kuyruk satırı istenen oranı global tavanla yan yana gösteriyor |
| **Fatura iptali silme değil** | `status='cancelled'` + gerekçe; yerine geçen fatura `replaces_invoice_id` ile öncekini işaret eder |
| **Muhasebe bildirimi satırdır** | Tarih, kanal, adres, kişi ve **yazışma thread'i**; müşteri sorgusu bu alanı seçmez (M-42). Adres tanımlı değilse gönderilmez |
| **Maliyet salt okunur** | `TrainingCostForSales` okur, satışta yazan action yoktur — reverse-grep testi bunu koruyor (M-36) |

⛔ **Teklif ve fatura tenant tablosu DEĞİL** (P-4). `customer_id` işaretçidir, RLS sürmez: bir teklif hiçbir müşteriye ait olmadan taslak olarak doğar ve maliyeti fiyatının yanında taşır. Müşteriyi tutan tek şey **policy + query** — bu yüzden negatif test policy katmanında (`QuoteIsolationTest`), ve `sameCompany()` müşterinin ulaşabildiği her yetenekte ayrı ayrı sorulur.

⛔ **Ölçülen üç tuzak** *(01.08.2026)*:
1. `BelongsToTenant` `tenant_id`'yi **mass assignment'tan çıkarır**; `belongsToTheOffice()` null'ı "ofis satırı" saydığı için müşterinin talep kalemi sessizce ofis satırı olarak yazılıyor ve RLS reddediyordu. Müşteri tarafı artık `forceFill` ile tenant'ını **açıkça** yazıyor.
2. `customer.approve_quote` **MFA'ya bağlı** (`config/security.php`) — ikinci faktörü olmayan kişide izin etkisizdir. Testte MFA'sız kurulan fixture, yetki sorunu gibi okunuyordu.
3. Müşteri kimliğiyle yazılan her audit satırı **tenant ister**; teklif tenant taşımadığı için müşterinin onay/ret/revizyon action'ları kendi tenant bağlamı içinde çalışır.

📋 **Tasarımcıya gidecek liste yazıldı:** `docs/design/tasarim-revizyon-1.md` — 21 madde, üç grupta (karar gerektiren / eksik ekran / eksik durum) + planlama tarafı. ⛔ Karar gerektiren maddelerde kodda **en dar davranış** uygulandı ve hangisi olduğu listede yazılı; hiçbiri uydurulmadı.

⚠ **Uygulanmayanlar** — tasarımda var, bu turda yok: teklif PDF'i ve sürüm farkı görünümü (`13`), `14`'ün üç adımlı sihirbaz başlığı, "sizin için tespit ettiklerimiz" uyum kartları ve isimli katılımcı seçimi (şimdilik kişi SAYISI alınıyor). ⛔ Bunlar tasarımcıya sorulacak liste ile birlikte aşağıda.

---

### Tahsilat, içerik paketleri ve teklif CRM'i (EGT-74, 01.08.2026 · Sets'in üç kararı)

Üç soru soruldu, üçü de cevaplandı ve uygulandı.

**1 · Tahsilat** *(Sets: "kısmi tahsilat olabilir, dekont yüklenebilir. biz ikisi için de hazırlığımızı yapalım ama zorunlu tutmayalım")*

| Karar | Özü |
|---|---|
| Kısmi tahsilat | `invoice_payments` satır satır: tutar, tarih, yöntem, not, opsiyonel dekont |
| "Tahsil edildi" TÜRETİLİR | Toplam tahsilat ≥ genel toplam. ⛔ `paid` kolonu yok — iki şeyin anlaşması gerekirdi, anlaşmadıkları gün yanlış olan müşterinin ekranındaki olurdu |
| Fazlası reddedilir | Fazla ödeme bir **alacak dekontudur**, başka bir işlem |
| Tahsilatlı fatura iptal edilemez | Ödeme, "borç yoktu" diyen bir belgeyi işaret ederdi; iki taraf iki farklı gerçeğe bakar |
| Müşteri ne görür | Kalan tutarı ve durumu görür; **yöntemi, dekontu ve kaydedeni görmez** — bunlar 3E'nin mutabakat kaydıdır |
| Vadesi geçen ≠ tahsil edilmiş | Tahsil edilmiş fatura tarihi ne derse desin "vadesi geçti" olmaz |

**2 · İçerik paketleri** *(Sets, mid-conversation düzeltmesi: "eğitim dokümanları başka, bunlar onlardan bağımsız içerik paketleri")*

⛔ **Üç ayrı şey ve karıştırılmayacak:** (a) eğitmenin sahaya inerken eriştiği **eğitim dokümanları**, (b) Bakanlığın düzenlediği **sertifika**, (c) 3E'nin ayrıca sattığı **içerik paketleri**. Üçü tablo paylaşmıyor.

| Karar | Özü |
|---|---|
| Ayrı satılabilir ürün | Kendi fiyatı var; hiç eğitim almamış firma da satın alabilir |
| Teklife kalem olarak girer | Kampanya yolu: `quote_items` artık **ya eğitim ya paket** taşır (CHECK ile tam olarak biri) |
| Onay lisansı açar | Müşteri teklifi onayladığı anda kütüphane açılır — ayrı bir adım, unutulacak bir adım olurdu |
| Erişim süresi ÜRÜNDE | Yıllık el kitabı ile tek seferlik kontrol seti aynı saatte satılmaz |

⛔ **Paket teklife KATALOGDAN eklenmez, teklif yazılırken eklenir** *(Sets, 01.08.2026)*. Tasarım `35`'te
katalogdan "Teklife Ekle" deyip hangi teklife ekleneceğini soran bir modal var; **uygulanmayacak** — satışçı
katalogdan başlayıp teklif aramaz. Doğru yer `31`'in kalem ekleme yolu ve orada **iki seçenek** olmalı:
**"Eğitim Ekle"** / **"Paket Ekle"**. Katalog ekranı ürünü incelemek içindir.

📄 Brief: `docs/design/teklif-paket-kalemi-brief.md` *(01.08.2026, tasarımcıya verildi)*.

✅ *(21.10.2026'da doğrulandı: `AddPackageToQuote` artık `QuoteController`'dan çağrılıyor; aşağıdaki boşluk kapandı.)* ~~⚠ **Bugünkü boşluk:** `AddPackageToQuote` action'ı **yazılı ama çağıranı yok**;~~ `AddQuoteItem` ise
`content_package_id`'yi sabit `null` yazıyor. Yani paket kalemi şemada ve teklif ekranının çiziminde var,
**oluşturulamıyor**. Kapanış adresi: bu revizyon.

⚠ **Ekran durumu (01.08.2026):** veri katmanı ✓, backoffice ✓ (Filament), müşteri kütüphanesi `16` içinde ✓, filigran ✓.
⛔ **Eksik olan ofis satış yüzeyi:** tasarım `35 Icerik Paketleri` bugün geldi — **paket kataloğu** (`/satis/paketler`),
**paket detayı**, **"Teklife Ekle" modalı** (hangi teklife) ve **müşteri vitrini** (`/portal/icerik/katalog`) çizili ama
kod yok. Bu satır düşmüştü; ekran tablosuna `35` olarak eklendi.
| Lisans tenant'a bağlı ve RLS'li | Hangi firmanın neyi lisansladığı başka firmanın işi değil |
| Her okuma sayılır | `content_access_events` — görüntüleme ve indirme ayrı ayrı, süresiz. ⛔ Erişim logu (`audit_access`) 30 günlük; lisans sorusu yıllar sonra sorulur |
| İndirilen kopya iz taşır | Her indirme kendi jetonunu alır; jeton hem kayıtta hem dosya adında |
| Link paylaşımı yok | Dosya bizim üstümüzden, lisans + tenant + kişi kontrolünden sonra sunulur |
| Katalog backoffice'te | Fiyat listesi gibi 3E'nin kendi referans verisi; ofis okur, tanımlamaz |

⛔ **Filigran uygulandı** *(Sets, 01.08.2026 — üç soru soruldu, üçü de cevaplandı)*.

| Karar | Özü |
|---|---|
| Araç | **Sunucuda `qpdf`** — ticari PHP lisansı yerine sistem paketi. Ücretsiz FPDI modern PDF'leri okuyamıyor; damgalamayı bu yüzden kütüphane değil araç yapıyor. Örtü PDF'ini FPDF üretiyor (kaynağı hiç açmıyor — ücretsiz sürümün yettiği tek kullanım bu) |
| Ne yazıyor | Lisans cümlesi + **opak anahtar**, iki ayrı yerde (üst sol, alt sağ) + QR. ⛔ **Ad yok, e-posta yok, firma adı yok**: sızan belge yabancıların elindeki belgedir; oraya bir çalışanın adresini basmak, lisans ihlalini önlemeye çalışırken kişisel veri ifşası üretmek olurdu. Anahtarı yalnız 3E `content_access_events` üzerinden çözer |
| Görüntüleme damgalanmıyor | Damga **çıkan kopyanın** peşine düşer; ekranda okunup indirilmemiş belge çıkmamıştır |
| PDF olmayan dosya | Değiştirilmeden dönüyor — elektronik tabloya görsel damga basılamaz. Ekranın "her dosyada filigran var" demesi engellendi |
| Araç yoksa | **Hata veriyor, damgasız sunmuyor.** Sessiz geri dönüş, ekranın hâlâ "izlenebilir" dediği damgasız bir kopya üretirdi |

⛔ **Üç şey ölçüldü ve üçü de sessiz hatalardı:**
1. **FPDF otomatik sayfa sonu** — 285mm'deki her işaret YENİ SAYFA açıyordu; örtü üç sayfa oluyor, qpdf de ilk (boş) sayfayı bindirip **başarılı** diyordu. Damga yoktu ve hiçbir yer bunu söylemiyordu.
2. **Türkçe karakterler** — FPDF çekirdek yazı tipleri WinAnsi; `ı ğ ş İ` içinde yok, damga "lisansÄ±" çıkıyordu. **Yalnız damga** harf çevirisiyle basılıyor; ekrandaki cümle tam Türkçe kaldı.
3. **QR düz siyah kare** — ilk sürüm SVG çıktısından `<rect>` ayıklıyordu; kodlayıcının **matrisi** kullanılıyor artık. Bir bakışta QR gibi görünen siyah kare, "QR var" diye yayına giderdi.

⚠ **Önbellek kararı ölçümle değişti.** Plan "firma damgası önbellekte + indirme başına QR" idi; qpdf'in maliyeti **örtüye değil yeniden yazdığı kaynağa** orantılı, yani önbellekli taban iki geçiş demek olurdu. İkisi de tek geçişte basılıyor. Büyük el kitabında sorun çıkarsa çözüm önbellek değil, **firma başına damgalamak** ve izin kişiye değil firmaya gitmesini kabul etmektir — bu bir karardır, optimizasyon değil.

⚠ **Yeni bağımlılıklar:** `setasign/fpdf` (örtü üretimi), `bacon/bacon-qr-code` (QR matrisi), ve sunucuda `qpdf`. Üçü de ücretsiz.

**3 · Teklif ekranının CRM yüzeyi** *(Sets: "eksiksiz olarak tamamla")*

| Parça | Durum |
|---|---|
| E-posta gönderme penceresi | ✓ ⛔ **Mektup sürüm DEĞİLDİR**: `gonder` metni dondurup saati başlatır, `eposta` hatırlatma yollar ve hiçbir şey dondurmaz |
| Görüşme notları | ✓ `record_notes` — telefon / ziyaret / iç not. ⛔ İç not hiçbir müşteri sorgusunda seçilmez; test tabloyu adı geçen dosya üzerinden arıyor. ⛔ Not **düzenlenemez ve silinemez** — sonradan yazılabilen anlık not, anlık not değildir |
| Teklife bağlı görevler | ✓ Aynı görev altyapısı (`TaskKind::QuoteFollowUp`), ayrı bir liste değil |
| Zaman akışı draweri | ✓ Beş kayıttan **birleştirilir**, saklanmaz — tablo olsaydı ikinci bir kopya olurdu ve sapan hep kopyadır |

---

### Beş tasarım uygulandı — `24` · `25` · `65` · `66` · `67` (EGT-72, 30.07.2026)

Sets: *"24,25,65,66,67 ile ilgili tüm kararları aldık, bunları eksiksiz olarak tamamla."* Uygulandı; kararlar ve **çıkan gerçek boşluklar**:

| Tasarım | Ne uygulandı | Bu turda kapanan boşluk |
|---|---|---|
| `25` Tedarikçi detay | Künye şeridi + iki kolon (sekme yok), fiyat grafiği (nötr, son teklif işaretli), **bölge ortalaması** (aynı il + tür, son 6 ay, kendisi hariç), düzeltme izi satırda, anlaşma yeşil çerçeve + geçmiş, belgeler, kullanıldığı eğitimler. Ekleme drawer'ı: web sitesi + standart indirim + not + **işaretle açılan ilk fiyat teklifi aynı istekte**; kayıt sonrası detaya gider | Düzeltilen fiyatın **eski değeri hiçbir yerde görünmüyordu** — düzeltmenin silmeden yapılmasının sebebi tam olarak buydu. Denetim tablosundan okumak denendi, tenant guard **haklı olarak** reddetti (audit tenant kapsamlı, tedarikçi ekranı tenant'sız); gösterim satıra yazıldı, kayıt yine audit'te |
| `24` Eğitim planlama | Solda iş / sağda sabit sütun; mevzuat **kart** (tanım sayısıyla), katlanabilir eğitim satırları, mevzuat uyarı paneli satırı takip ediyor, **madde referansı**, bildirim son günü hesabı + "tarihi öner", paket kartı ikinci satırla belirir, iki sayaçlı kaydet barı, "Planlandı'ya al" onaylı ve tek istekte | Mevzuat uyarıları ve dokümanları ekrana **hiç gelmiyordu**; `RegulationOptions` yalnız ad/kod veriyordu. Sınav kuralı da planlama anında görünmüyordu |
| `65` Eğitmen ekleme | Drawer değil **sayfa**; üç bölüm + canlı gereklilik paneli (gereklilik bir kez, isteyen eğitimler yanında, "şunlardan biri yeterli"), belge istemeyen gereklilik **görev açmaz**, **üç cevaplı satır** (şimdi yükle / görev: ben / görev: eğitmen), "kaydettiğinizde ne olacak" özeti. **31.07.2026'da tasarımın kompozisyonuyla yeniden yazıldı** — iki kolonlu ızgara, Bakanlık ID ↔ sertifika aynı satırda, hassas alanlar KVKK rozetiyle kendi kuralının altında, panel bölünmüş tek liste + durum rozeti, sticky kayıt şeridi | Her owner-yüklenebilir tür **eğitmene** açılıyordu; yöneticinin kendi üstlendiği belgenin hiçbir yerde hatırlatıcısı yoktu — ve aday listesinin beklediği belge tam o belgeydi. **31.07:** tasarımın üçüncü cevabı ("şimdi yükle") hiç yoktu — elindeki sertifikayı yükleyebilmek için yönetici kaydı kaydedip tekrar bulmak zorundaydı, arada açılan iki görevi biri kovalıyordu. Dosyalar formla birlikte gidiyor; staging store yok |
| `66` Onaylar | Master-detail; tür sekmeli kuyruk (SLA aşımı, eskalasyon, kendi işi kapalı), **türe göre kanıt paneli** (masraf: kalem tablosu + o ana kadar onaylanmışlar + cari + fiş önizleme; belge: karşıladığı gereklilik ve açtığı adaylıklar; iptal: bakanlık etkisi; maliyet eşiği: toplam/eşik), gerekçeli ret + hazır gerekçeler, "onaylarsanız ne olacak" | Kuyruğun kendisi yoktu. Ayrıca **iptal talebinin sahibi ve zamanı hiç yazılmıyordu** — ikisi de kuyruk satırının şartıydı ("3 gün bekliyor", "sizin talebiniz") |
| `67` Eğitmen dosyası | Üç ayrı sayfa (dosya · finans · loglar), düzenlenebilir künye, **maskeli TCKN/SGK + loglanan açma eylemi**, yetkinlik tablosu (satır satır sebep, **kısmi engel**), yaklaşan atamalar, finans özeti; finans: dört rakam + program→eğitim→masraf üç kademe + elle hareketler + ödemeler + **kalem seçimli ödeme kaydı** + elle hareket paneli | Kimlik bilgileri **yalnız oluşturma anında** düzenlenebiliyordu: yenilenen sertifika, geç gelen Bakanlık ID ve değişen tarife — hepsi atama kapısının girdisi — için ekran yoktu. Cari (`InstructorLedger`) ve hakediş tablosu da yoktu |

**Yeni yetki adları (AD-23):** `instructor.update` (dosyayı düzeltmek — atamaları açıp kapatabilir), `instructor.reveal_identifier` (maskeli numarayı açmak, her açılış loglanır), `instructor.view_ledger`, `instructor.record_payment`. `instructor.request_documents` matristen çıkıp **standalone workflow** oldu: başkasına iş çıkarmak, kayıt üzerinde bir fiil değil.

**Maliyet eşiği onayı (spec §7.4–7.5):** `PlanningApproval` saf sınıfı üç mod (`never` / `always` / `above_threshold`) + planlamacı override'ı; karar **VERİLEN TUTARLA** saklanıyor — tutar değişince onay düşüp kayıt kuyruğa geri giriyor. Bir eşiğin altında planlayıp farkı sonradan kapatma yolu böyle kapandı.

**Hakediş saat ya da gün üzerinden hesaplanır, tutar donar** *(Sets, 02.08.2026)*. Yönetime sunulan model: eğitmenlere **saatlik ücret** girilir, verilen saat üzerinden hesaplanır (yönetim sıcak bakıyor). ⚠ Şema ikisini de taşıyor: `instructor_category_rates` **baz** tarifedir (backoffice), `instructors.rate_unit/rate_amount` **eğitmene özel** anlaşmadır; `EffectiveInstructorRate` "özel varsa özel, yoksa baz" der. Hakediş satırı `rate_unit + rate_amount + hours + days + basis` ile **donar** — tarife sonradan oynayınca geçmiş hakediş kendini yeniden yazmaz.

**Masraf ve hakediş iki ayrı kalemdir, ilişkili gösterilir** *(Sets, 02.08.2026)*: *"A eğitiminin hakedişi ve masrafı, B programının hakedişleri ve masrafları (altında eğitim bazında kırılımıyla)"* + toplam + yapılan ödemeler. ⛔ Tek satırda toplanmazlar.

**Hakediş, ofisin eğitimi TAMAMLAMASIYLA doğar** *(Sets, 02.08.2026)*. Eğitmenin "Eğitimi Tamamla"sı teslimdir, hakediş değil; ofis çelişkileri çözüp KUMS kanıtını ve sertifikayı bağladıktan sonra (`FinishTraining`) hakediş doğar. ⚠ Eğitmen bekler, ama kanıt zinciri temiz kalır: teslimde doğsaydı ofis tamamlamada eksik bulduğunda doğmuş bir alacağı geri almak gerekirdi. ⛔ Müşteri faturasına da bağlanmaz — eğitmenin alacağı müşterinin ödeme davranışına bağlanamaz.

**Uygulandı** *(02.08.2026)*: `FinishTraining` artık `RecordInstructorEarning` + `SettleInstructorEarning` çağırıyor — onaylı eğitmen, oturum programından türeyen **saat ve gün**, `EffectiveInstructorRate` ile tutar. ⚠ Tarifesi olmayan eğitmende eğitim yine kapanır, hakediş doğmaz: bir finans ayarı sertifikayı rehin alamaz. Bölge tercihi (`instructor_travel_provinces`, boş = tüm Türkiye) eğitmen dosyasına ve aday listesine eklendi — aday **listeden düşmez**, "bu ile genelde gitmiyor" diye uyarılır.

**Hakediş (`instructor_earnings`) — tablo var, doğuş anı zincire bağlı.** İki durum: `pending` (eğitim gerçekleşti, evrak onaylanmadı — tutar görünür, borç değil) ve `earned`. Dayanak satıra donuyor (birim + tutar + cümle), yoksa tarife oynayınca geçen yılın hakedişi kendini yeniden yazardı. `RecordInstructorEarning` ve `SettleInstructorEarning` **omurga**: çağıranı günü kapatma akışı, o da katılımcı zincirinde (EGT-73).

### `24 v1.1` — eğitmen satıra geldi (31.07.2026)

Sets: *"planlamanın işi eğitmen ve programı eşleştirmek, adam zaten bunun için çalışıyor ve `24`'te eğitmen seçebileceği yer yok?"* Tasarımcı `24`'ü revize etti (`design/24 … dc.html`); uygulandı.

**Akış — spec §6.1'in aynısı, ekran onun üstüne oturuyor:**

```
Eğitim → atama isteği (kaç eğitmen) → N adaya teklif → kabul → kesinleşmiş atama
```

Satırdaki eğitmen seçimi **kimin çağrılacağıdır**; kaydedince istek `selection` modunda açılır ve seçilenlere **davet gider**. Kesinleşme kabul + seçimle olur (§6.2). Ekranda tek cümleyle yazıyor *(Sets'in kendi ifadesi)*: **"Seçtiğiniz eğitmenlere davet gider; kabul ederlerse aralarından birini seçip bağlarsınız."**

⛔ **Kaydetme anında uygunluk YENİDEN denetlenir.** Planlamacının okuduğu liste bir an eskidir; arada belge düşmüş olabilir ve Y-2016 md.13/d uygunsuz eğitmeni *kötü tercih* değil **ihlal** sayar. Ekranın nezaketine bırakılmıyor.

⛔ **Uygun olmayanlar varsayılan olarak listelenmez** — K-1 duruyor. *(Sets, 31.07.2026)*: planlamacı **filtreyi kaldırıp** gerekçeyi görebilir (*"belgesi yok — CTU süresi doldu"*, *"takvimi dolu — 24.08 Samsun"*), **ama seçemez**. Gerekçesi: *"böylece planı taslak olarak tutup eğitmene evrak tamamlattırma şansı olur."* İki koruma: gerekçeler yalnız `instructor.view` yetkisi olana üretilir (dosyayı okuyamayan içeriğini aday listesinden öğrenmez), ve uygun olmayan **sunucuda da** reddedilir.

⚠ **Tarih değişirse satırdaki seçim düşer** ve planlamacıya söylenir — üç sorunun (yetki · belge · takvim) hepsi tarihe bağlı; sessizce kalsa ekran kadrolu görünen boş bir plan gösterirdi.

**İlk turda kaçırılan ve bu turda kapatılanlar:** bağlam şeridi (`Kaynak · mevzuat · yer`) · `← Kuyruğa dön` · `düzenleniyor` etiketi · tarih aralığında `N gün` okunuşu · `maddeyi aç` bağlantısı · **müşteriler salt okunur** (planlamacı müşteri seçmez — `HANDOFF` kural 5) · sağ sütunda *"Eğitmen burada atanır"*.

⚠ **Kuyruktan gelme hâli** (onaylı teklifle açılış: kilitli müşteri, öneri tarih/kontenjan) yazılmadı — besleyecek satış modülü ve kuyruk yok.

---

### Referans kodu — program üyeliği koda girdi (AD-19 revizyonu, Sets 31.07.2026)

**Karar:** kod **form hâlindeyken üretilmez**; taslak olarak bile kaydedilir kaydedilmez üretilir ve `(yıl, son ek)` uzayında rezerve edilir. Bu zaten böyleydi ve doğrulandı — taslak `PL-…` taşır, Planlandı'ya geçince **son eki korunarak** `EG-…` olur.

**Yeni şema:**

```
PR-2026-7K4F            program            (PK → PR: herkesin kullandığı kelime "program")
EG-2026-7K4F-01         programdaki 1.     ← programın son eki + sıra
EG-2026-7K4F-02         programdaki 2.
EG-2026-9M2X            tek eğitim, program yok — düz biçim korunur
```

⛔ **Neyi çözüyor:** eskiden `PK-2026-7K4F` ile `EG-2026-9M2X` arasında **görünür hiçbir bağ yoktu** — kodu eline alan "bu hangi haftanın eğitimi" diyemiyordu, ki bir eğitim kodu hakkında sorulan ilk soru budur.

⚠ **Son ek rastgele kalır, yalnız sıra sıralıdır.** R-2: sıralı kod yürünebilir. Sıranın sıralı olması güvenli, çünkü kardeşi bulmak yine programın rastgele son ekini gerektirir. (Sets'in örneğindeki `0417` alfabede yok: `0` ve `1` `O`/`I` ile karıştırıldıkları için dışarıda.)

⛔ **Bu turda önlenen kusur:** son ek koda `son tireden sonrası` alınarak yazılıyordu. `EG-2026-7K4F-01`'de bu `01` demekti — ve `(yıl, son ek)` tekil olduğu için **her programın birinci eğitimi birbiriyle çakışırdı**. Çıkarma kuralı artık "yıldan sonrası" (`ReferenceCode::suffixOf`), ve testi var.

⚠ **Taşıma diye bir şey yok** *(Sets, 31.07.2026)*: bir eğitim programdan çıkarılırsa çıkarılmıştır; başka programa konan eğitim yeni ve bağımsız bir kayıttır. Dolayısıyla kodun program üyeliğini taşıması yalan söyleyemez.

---

**Elle kayıt — bağlam üç durumdur** *(Sets düzeltmesi, 30.07.2026)*. İlk yazımda eğitim ve paket **sabit `null`** yazılmıştı; Sets: *"elle girilen ödemede paket veya eğitime bağlı olabilir ama olmak zorunda da değil diye konuştuk; tasarımcı zaten bu yüzden program/eğitim select'i koydu, boş bırakılabilir diye."* Doğrusu: **program · eğitim · bağımsız** — ikisi birden asla (iki tarafı toplayan sorgu tutarı iki kez sayar, iki tabloda da CHECK var).

Aynı panelde **iki kayıt tipi** var (masraf | ödeme) ve panelin kuralları ikisi için de aynı: not zorunlu, onaya gider, onaylanana kadar bakiyeye girmez, giren onaylayamaz. Bu yüzden:
- **Ödeme kaydı artık onay durumu taşıyor** (`instructor_payments.review_state`). Elle girilen ödeme `pending` doğar ve **bakiyeden düşmez**; dağıtımlı ödeme panelinden girilen havale `approved` — o bir iddia değil, dekontu ve dağıtımı olan bir işlem.
- Bekleyen ödeme **masraf havuzunda** kuyruğa giriyor (§7.8 tek para havuzu tanımlıyor, `66`'da altıncı sekme yok). Onay/gerekçeli ret: `ApproveManualPayment` / `RefuseManualPayment`; **kendi girdiğini onaylayamaz** — burada "kendi" kaydı GİRENDİR, senaryonun tamamı bir meslektaşın başkasının yaptığını kaydetmesi.
- ⚠ Elle masraf girişinde **kalem türü zorunlu** — tasarım panelinde bu alan yok ama kolon NOT NULL; sessizce "diğer" adında bir kalem türü uydurmak AD-08'e aykırı olurdu.

---

### 4.6 Ofis — eğitmen

**Ne yapar:** atama tekliflerini kabul/ret eder, saha işini yürütür, masraf girer, kendi carisini görür.

**Durum:** eğitmen kaydı ✓ · belge akışı ✓ · **eğitmen paneli ✓ (EGT-66)** · **panel sayfaları + masraf girişi ✓ (EGT-67)** · masraf onayı ✓ (`ApproveExpense`/`RefuseExpense`, onay kuyruğunda) · cari ✓ (`instructor_earnings`, `instructor_payments`, `/egitmen/cari-durumum`) · saha ekranı ✓ (`Instructor/TrainingDay`, 375px kart görünümüyle)

**Eğitmen paneli — beş ekran** *(EGT-67, 30.07.2026)*. **Hiçbir izne bağlı değil** — gördüğü her şey kendi kaydı; kayıt yoksa 404. Menüye de izinle değil kayıtla giriyor.

| Ekran | Ne var |
|---|---|
| `/egitmen` | Bekleyen teklifler, kabul/ret (gerekçe zorunlu), yeniden onay; diğer sayfalara link kartları |
| `/egitmen/egitimlerim` | Kesinleşmiş atamalar, yaklaşan/geçmiş ayrımı |
| `/egitmen/takvim` | Ay ızgarası; eğitim ve izin **ayrı renkte**, tek "dolu" işaretine indirilmez |
| `/egitmen/masraflarim` | Masraf girişi ve geri çekme (spec §7.9) |
| `/egitmen/musaitlik` | İzin girişi |

⛔ **Neden tek kokpit değil:** *"ilk adımda ne yaptığını 5. adımda unutur"* mantığının panel karşılığı — sahada bağlantıyla açan kişi tek bir soruya bakıyor (*"salı günü neredeyim"*), dört bloklu tek sayfa onu üç cevabın altına gömüyordu.

**Menü gruplaması** *(`05 Navigasyon`, EGT-67)*: GENEL / PLANLAMA / EĞİTMEN / YÖNETİM; microcaps etiket + hairline, grup başına en fazla 5 kalem, **boş grup hiç render edilmez** (eğitmen olmayan "EĞİTMEN" başlığını görmez). Gruplama `OfficeNavigation`'da veri, markup'ta değil — yeni modül gruplandırılmadan menüye giremez.

**Masraf girişi** *(spec §7.9 — kapsam revizyonu, Sets 30.07.2026)*: spec masraf **girişini** kapsam dışı bırakmıştı, onay tarafını (§7.6–7.8) tanımlamıştı. Girişsiz onay ekranının onaylayacağı kayıt hiçbir yerden doğmuyordu; yoldaki eğitmenin tek yolu fişi birinin telefonuna göndermekti. Kayıt karar kolonlarıyla doğar, `submitted` kalır; onay/cari EGT-28'in. Limit **işaretlenir, engellenmez** (K-19); fiş zorunluluğu ayardan (AD-08); silme yok, `withdrawn` var (AD-11).

**Eğitmen dosyası** (`/egitmenler`, operasyon): liste + dosya ekranı, evrak durumu, **evrak isteme**. İki ayrı izin: `instructor.view` okur, `instructor.request_documents` başkasına iş çıkarır.

**Müsaitlik** *(Sets, 30.07.2026 — spec §3.1.1)*: uygunluğun üçüncü kaynağı. Eğitmen izin girer, o tarihlerde aday listesinde görünmez; gerekçe planlamacıya gösterilmez; kesinleşmiş atamayı düşürmez.

**Evrak görevi** *(spec §6.14)*: kayıt açılışında sahibinin yüklemesi gereken evraklar görev olarak düşüyor, operasyon sonradan da isteyebiliyor. Görev **sahibine** düşer.

| Karar | Kaynak |
|---|---|
| Eğitmen bir **ofis kullanıcısıdır**; dosyası ayrı tabloda yaşar ve kişi ayrılsa da kalır | `egitmen-spec.md` §1, `create_instructors.php` |
| Kategoriler: sürekli / bağımsız-sözleşmeli / proje bazlı | çatı §174 |
| Kadro değişikliği (giriş **ve** çıkış) 15 gün içinde bildirilir — görev üretir | Y-2016 md.13/j, `ChangeOfficeAccountStatus.php` |
| Belgeleri eğitmen değil **yönetici yükler**; yükleme görevi de açılabilir | `HANDOFF.md` §5, Sets 28.07.2026 |
| Birim eğitmen bazında seçilir (saatlik/günlük); boş bırakılırsa kategori tarifesi uygulanır | `egitmen-spec.md` §4.1 |
| Kabul/ret **yalnız uygulama içinden**; mail hiçbir atama durumunu değiştirmez | `egitmen-spec.md` §6.13, K-12 |
| Kabul öncesi katılımcı **sayısı** görünür, listesi görünmez; kabul sonrası açılır ve erişim loglanır | `egitmen-spec.md` §6.9, K-10 |
| Cari borcu elle açılamaz — yalnız hakediş ve onaylı masraftan türer | `egitmen-spec.md` §7.7, K-21 |
| Ödeme kaydı hangi kalemleri kapattığı seçilmeden kaydedilemez | `egitmen-spec.md` K-22 |
| TCKN/SGK maskeli gelir; açık görüntüleme ayrı eylemdir ve erişim loguna yazılır | `egitmen-spec.md` K-24, AD-21 |

**Veri:** `instructors`, `instructor_documents`, `instructor_document_versions`, `instructor_unavailability`, `instructor_expenses`, `instructor_expense_receipts` ✓ · cari tabloları ✓ (`instructor_earnings`, `instructor_payments`)

**Ekranlar:** `40 Egitmen Paneli` ✓ (beş ekrana bölündü) · `41 Egitmen Saha` ✓ (matristeki beş açık uç 01.08.2026'da kapandı)

**Oturumlar elle yazılmaz, türetilir** *(Sets, 01.08.2026)*. `SessionPlan` + `ScheduleSessions`: eğitim tanımının `initial_hours` ÷ `max_daily_hours` (TY-2019 md.17/1 · 8 saat, **admin alanı**) gün sayısını verir; `session_hours` doluysa gün o kadar oturuma bölünür, boşsa gün tek oturumdur. Saat başlangıcı `training.day_starts_at` ayarı. Planlama ve plan güncellemede koşar; ⛔ **kimse işaretlenmişse programı yeniden yazmaz** — o kayıt sertifikanın dayanağıdır.

**Ücret eğitmen ekranında görünmez** *(Sets, 01.08.2026)*. Ücret/maliyet alanları yalnız planlama aşamasında ve yetki sahibindedir; eğitmen kendi alacağını `Cari Durumum`'dan görür. Tasarım `41`'in "ücretin ₺11.000/gün" satırı bu yüzden uygulanmadı.

**Fotoğraf netliği tarayıcıda ölçülür, OCR yok** *(Sets, 01.08.2026)*. `lib/sharpness.js`: yükleme ÖNCESİ, küçültülmüş kopya üzerinde Laplacian varyansı; eşik `business.upload.sharpness_threshold` (admin), `FileDrop`'a ortak Inertia prop'undan gelir — sayfa taşımaz. ⛔ **Engellemez**, uyarır. ⛔ **Fişten veri okunmaz**: fiş tek tip değil (fatura, A4, çakmak boyu slip, belediye otoparkı formatı), kalemler her birinde farklı yazıyor; tutarlı çıkarım üretilemez ve yanlış doldurulmuş tutar boş alandan kötüdür.

**Harita: pin varsa gömülür, yoksa açık adres** *(Sets, 01.08.2026)*. `trainings.map_url` planlamacının yapıştırdığı konum bağlantısı. ⛔ Gömme yalnız tanınan sağlayıcılara yapılır — planlamacının elle yazdığı rastgele bir URL'i iframe'e koymak sayfayı o host'a teslim etmektir.

**Gün kapanışı ve teslim — iki ayrı buton** *(Sets, 01.08.2026)*. Eğitim yarım gün de olabilir, 20 saatlik eğitimde 2,5 gün de sürebilir; bu yüzden **"Günü Kapat"** o günün yoklaması tam ve o günün imzalı listesi yüklüyse basılır, **"Eğitimi Tamamla"** ise tüm günler kapandıktan (ve sınav varsa notlar girildikten) sonra teslimi bildirir. ⛔ İkisi de "eğitim tamamlandı" DEĞİLDİR — o kapı ofiste (`FinishTraining`, çatı §143): çelişki çözümü + KUMS kanıtı + sertifika. Teslim `trainings.delivered_at`'e yazılır, statüye değil; ofise `training.completion_check` görevi düşer. Salonda hiç görünmeyen katılımcıyı `no_show` yapan tek yer teslim anıdır — kararı odadaki kişi verir.

**İmzalı yoklama listesi güne bağlı.** `training_registers.covers_on` (01.08.2026): 2,5 günlük eğitim üç ayrı liste gönderir; günsüz kayıt ilkinin yeniden taranmışı sanılırdı. Ofisin `22`'den yüklediği eski satırlar `null` kalır.

**Eğitmen materyali eğitim TANIMINA bağlı** — `training_materials` (sunum, sınav seti, yoklama şablonu). `audience` alanı taşıyıcı: `instructor` cevap anahtarını kapsar ve **byte servis edilirken tekrar sorulur**, yalnız kartta değil. Yükleme/güncelleme sadece backoffice'te (`TrainingDefinitions → Materyaller`). `is_watermarked` erişim kontrolü değildir; sızan kopyayı izlenebilir yapar.

**Eğitmen yetkisi izin değil sahipliktir** — `InstructorOwnership::teaches()` (onaylı atama). Yoklama işaretleme, sınav notu, imzalı liste ve materyal okuma bu kapıdan geçer; `SessionAttendancePolicy` ve `ExamResultPolicy`'de "41 ile gelecek" diye duran adres 01.08.2026'da kapandı.

**Ticket:** EGT-57 (spec) ✓ · EGT-59 (kayıt implementasyonu) ✓ · EGT-66 (teklif kabul/ret) ✓ · EGT-67 (panel sayfaları + masraf girişi) ✓

---

### 4.7 Müşteri portalı

#### E-posta müşteri tarafında SİSTEM GENELİNDE tektir *(Sets, 16.09.2026, EGT-208)*

⛔ **Bir adres yalnız bir firmada olabilir** — durumu ne olursa olsun (askıdaki kayıt da adresi tutar). Yazılı karar tersiydi (omurga-spec §3.2 I-2, §13: *"farklı tenant'larda aynı e-posta ayrı kişidir"*) ve giriş ekranı buna göre kurulmuştu: şifre bütün kayıtlarda denenir, **tam bir** kayıtta tutarsa giriş. Ölçüldü: Tayfun aynı adresi üç firmaya ekledi, iki aktif kayıtta aynı şifre → giriş hiç çalışmadı, ekran «şifre yanlış» dedi (17 red). Sets: *"adam login olmadığı sürece tenant'ı bilmiyoruz; beş tane aynı mail varsa hangi tenant olarak login olmaya çalıştığını nereden bileceğiz?"*

⛔ **Uygulama:** `customer_users.email_hash` global unique (`2026_10_27_000100_an_address_belongs_to_one_company`). `App\Support\Customers\AddressRegistry` yazan her yere sorar — panel ekleme formu, düzeltme çekmecesi, Excel aktarımı (satır «Bu e-posta adresi kayıtlı» işareti alır), ofis «kişi ekle» kutusu, profil adres değişikliği. Mesaj her yerde aynıdır ve **firma adı söylenmez**; kabul edilen ifşa yalnız «bu adres kayıtlı» cümlesidir. Sorgu, giriş kapısının kullandığı `app.authenticate_lookup` üzerinden gider — ikinci bir güvenlik tanımlı fonksiyon açılmadı.

⚠ **`sets@x` ile `sets+test@x` iki ayrı adrestir.** Hash adres yazıldığı gibi (küçük harf, boşluksuz) alınır; etiketin aynı kutu olup olmadığına posta sağlayıcısı karar verir. Bu, bir kişinin test hesabı ile gerçek hesabını ayırmasına izin verir.

⚠ **Değişmeyen:** kimlik alanları arası serbestlik (ofis/backoffice/müşteri tablolarında aynı adres çakışma değildir) ve «kayıtlar tenant'lar arası ilişkilendirilmez» kuralı. Giriş kodundaki «tam bir eşleşme» dalı duruyor; artık en fazla bir kayıt olduğu için ölü daldır, sadeleştirme ayrı iş.

⚠ **Canlı temizlik (16.09.2026):** Tayfun'un üç kaydından ikisi silindi (Vitsan Gözetim, deneme); **Vitsan Mümessillik'teki silinemedi** — o hesapla hukuki metin onaylanmış ve onay kayıtları veritabanı trigger'ıyla silinmeye kapalı (`deny_consent_delete`, AD-11). Kalan kayıt aktif, adres tek.

#### Posta ulaşmayan adres: davet reddedilir, giriş ekranı susar, liste rozet gösterir *(Sets, 16.09.2026, EGT-207)*

⛔ **Doğrudan giden her posta artık bastırma listesinden geçer** (`App\Support\Notifications\Deliverability`). Bildirim işçisi ve yazışma işçisi listeye zaten bakıyordu; davetler, sihirli bağlantılar ve şifre sıfırlamaları `notify()` ile bakmadan çıkıyordu. Ölçüldü (canlı, 16.09.2026): `verigiris+1@3egroup.tr` adresine davet sert döndü (3egroup.tr'nin posta sunucusu artı-adreslemeyi tanımıyor), Postmark adresi kapattı, ikinci «Davet et» tıklaması yakalanmayan `TransportException` → 500 sayfası (OPS-26 aynı olay).

⛔ **İki davranış, ekrana göre.** Söyleyebilen ekran söyler: panel daveti, ofisten davet, ofis kullanıcısı daveti ve profildeki e-posta değişikliği — «Bu adrese gönderilen posta :tarih tarihinde geri döndü; adresi düzeltin» uyarısı, işlem geri alınır, bağlantı yazılmaz. Ofis kullanıcısı **açılmadan önce** de sorulur (form hatası). **Söylememesi gereken ekran susar:** müşteri/ofis sihirli bağlantı ve üç şifre sıfırlama ekranı adresin bizde olup olmadığını sızdırmamak üzere kurulu; kapalı adres için gönderim atlanır, log'a düşer, ekran her zamanki «gönderildiyse kutunuza bakın» cevabını verir.

⚠ **Rozet.** Panel personel listesinde ve ofis müşteri kartının kişi listesinde adresi kapalı kişinin yanında kırmızı «Posta ulaşmıyor» rozeti (tasarım dosyasında yok; Sets'in kararı). Adres düzeltilince hash değiştiği için kilit kendiliğinden kalkar.

⚠ **`mail+etiket@alan` biçimi bizde zaten geçerli** — kabul ediliyor, posta çıkıyor. Artı-adreslemeyi alıcı alan adının sunucusu karar verir; 3egroup.tr'ninki etmiyor. Test için Gmail/Workspace ya da gerçek kutu gerekir.

⚠ **Açık kalan:** aynı adresin sonradan çalışır hale gelmesi (3E BT artı-adreslemeyi açarsa) bizim listede `lifted_at` + Postmark tarafında yeniden etkinleştirme ister — ayrı iş.

#### Ekran gönderdiği postayı söyler *(EGT-213, 22.09.2026)*

⛔ **Personel eklendiğinde çıkan mesaj daveti de söyler:** *«Personel eklendi ve panel daveti e-posta ile gönderildi.»* Önceden yalnız *«Personel eklendi.»* diyordu ve kişiye aynı anda **davet maili gidiyordu** (30.08.2026'dan beri, EGT-201). Ekleyen kişi bundan habersiz kalıyordu; Tayfun'un bildirme şekli bunun kanıtı: *"RD'ye birkaç kişi mail geldiğini söylemiş fakat içeriği hakkında bilgim yok."* Yani hatayı bulan şey, karşı tarafın «bu ne?» diye sormasıydı.

⚠ **Cümle olan şeyi söyler, olacak şeyi değil.** Posta gitmediyse yazılmaz: adres kapalıysa sebebiyle uyarı çıkmaya devam eder (EGT-207), başka bir sebeple düşerse hata bize raporlanır ve başarı mesajı **davetten söz etmez**. Gönderilmemiş bir maili gönderilmiş diye yazan bir ekran, hiç söylemeyenden kötüdür.

⚠ Mailin içeriği baştan beri doğruydu — *«Hesabınızı etkinleştirmek için şifrenizi belirleyin»*, bağlantı 7 gün. Tasarımda da böyle yazılıydı (omurga §3.0.2, 27.07.2026: *«davet edilen yalnız şifresini belirler»*). Eksik olan **ekranın haberi**ydi, mekanizma değil.

#### Excel'den aktarım: önce göster, sonra yaz — ve eklenen personele mail gider *(Sets, 30.08.2026, EGT-201)*

⛔ **Düğme 20.08'de gizlenmişti (EGT-187), 30.08'de geri açıldı** — ama aynı akışla değil. Vitsan toplantısında firma *"personel listesini Excel olarak gönderelim, tek tek girmek bizim için zor"* dedi; açmadan önce Sets dört soru sordu (şablon güncel mi, doğrulama ekranı var mı, düzeltme yapılabiliyor mu, doğru datayı aldığımızdan emin miyiz) ve dördünün de cevabı **hayır** çıktı.

⛔ **Sıra artık şudur: yükle → oku ve yargıla → EKRANDA GÖSTER → düzelt → onay iste → yaz.** Eski hâlinde dosya yüklenir yüklenmez kişiler yaratılıyor, rapor sonra geliyordu — yanlış dosya geri alınamıyordu. Sets'in kuralı: *"datayı bir kez corrupt edersek sonradan çok uğraşırız"*. Okuma adımı (`ReadStaffSheet`) hiçbir şey yazmaz; yazma adımı (`ImportPortalPeople`) artık dosya değil, **bir insanın baktığı satırları** alır.

⛔ **Doğrulama ekleme formuyla birebir aynı katılıkta.** Kimlik numarası zorunlu ve doğrulama hanesiyle kontrol edilir, doğum tarihi zorunlu, telefon E.164'e çevrilir. Gerekçe kozmetik değil: **davet bağlantısı kimlik numarasının son dört hanesini soruyor**, yani yanlış numara kişiyi kendi ekranına kalıcı olarak sokamaz hâle getirir. Sunucu, ekranın işaretlediklerini **yeniden** doğrular — ekrandaki kırmızı kutu kullanıcıya nezakettir, kolonu koruyan şey istek kuralıdır.

⛔ **Personel eklemek artık davet gönderir — her iki yolda da** *(Sets: "sisteme eklenen personele mail gider? adam girip evraklarına bakar, eğitimlerine bakar? tüm sistem zaten bunun için kurgulandı")*. Bugüne kadar hiçbir bildirim gitmiyordu; bu bir karar değil, yazılırken kalmış bir boşluktu (yalnız iki yardım metninde cümle olarak duruyordu). Aktarımda davetler **kuyruğa** verilir — beş yüz mektup bir web isteğinin içine sığmaz.

⚠ **Toplu işlem ekranın parçasıdır** *(Sets: "grup olarak da seçebilsin, tümü de diyebilsin, tek tek de işlem yapabilsin")*: satır kutucukları, "tümünü seç", seçililere personel türü/departman uygulama, satır çıkarma. **Zorunlu evraklar dosyanın tamamı için tek soru** — 48 kişi × 4 evrak = 192 soru olurdu; varsayılan "evrak görevleri bana açılsın", sonradan personel sayfasından değiştirilebilir.

⚠ **Yol üstünde bulunan üç gerçek hata** (üçü de ekran fotoğrafı çekilirken çıktı, hiçbiri testlerin gördüğü yerde değildi): indirilen şablon **noktalı virgülle** yazılıyordu, okuyucu virgül bekliyordu — yani kendi şablonumuz kendi aktarımımızdan geçmiyordu; Inertia aynı bileşene dönerken `setup` yeniden koşmadığı için satır listesi boş kalıyordu; ve native `<select>`'te `value: null` taşıyan seçenek boş görünüyordu. ⛔ Sonuncusunun bir kardeşi **ofis tarafında duruyor** (`Office/Customers/Show.vue`, satışçı rol filtresi) — kapsam dışı bırakıldı, ayrı iş.

#### Personel kaydı doğum tarihi taşır — ekleme formunda zorunlu, düzeltmede değil *(Sets, 30.08.2026, EGT-202)*

⛔ **Bakanlık bildirimi personelin doğum tarihini de istiyor** (Tayfun'un bildirimi). Kayıtta böyle bir alan yoktu; `customer_users.birth_date` eklendi ve personelin sisteme girdiği **üç yolun hepsinde** soruluyor: firma panelindeki ekleme formu, ofisteki müşteri kartının «Kişi ekle» kutusu, Excel'den toplu aktarım.

⛔ **Zorunluluk yola göre değişir ve bu bilinçlidir.** Firma panelindeki ekleme formunda **zorunlu** — bakanlığa bildirilecek kişiyi giren yer orasıdır. Düzeltme çekmecesinde ve ofis kutusunda **zorunlu değil**: alan bugün geldiği için eski personelin hepsinde boş ve zorunlu bir kural o kişilerin adındaki yazım hatasını bile düzeltilemez kılardı; ofis kutusu ise çoğunlukla firmanın panel yetkilisini açıyor ve o kişi bakanlığa bildirilmiyor. Kolon **nullable** kalır: boş bir doğum tarihi *"bilinmiyor"* demektir, uydurulmuş bir tarih ise bakanlığa **gerçek** diye gider.

⚠ Aktarımda okunamayan/boş tarih satırı **düşürmez** — kişi girer, tarihi boş kalır. Kırk sekiz kişilik bir listeyi tek eksik hücre yüzünden geri çevirmek, firmayı listeyi yarıda bırakmaya iter.

⚠ **Açık kalan:** bakanlık bildiriminin kendisi bu alanı henüz **okumuyor** — bildirim bugün elle yapılıyor (`training_ministry_notifications` yalnız kimin ne zaman bildirdiğini ve KUMS referansını tutuyor). Doğum tarihini bildirim çıktısına koyan iş ayrı ticket'tır.

#### Teklif onayından sonrası MÜŞTERİNİNDİR *(Sets, 02.08.2026)*

⛔ **Planlamacı teklif öncesinde çalışır.** Sıra şudur: talep/potansiyel → planlama (tek eğitim veya program) → onaylanınca satılabilir eğitim → satışçı görüşür ve teklif eder (ya da müşteri açık eğitim için "teklif iste" der) → **teklif onayı** → katılımcı listesini **müşteri kendi panelinden** kurar → eğitmen sahada verir → ofis tamamlar → hakediş doğar. ⛔ Onaydan sonra planlamacıya da satışçıya da **iş çıkmaz**; ikisinin de işi bitmiştir.

**Onay anında iki tarafa görev düşer**: müşteriye "katılımcı listesi eksik" (yapacak olan odur) ve satışçıya aynı bilgi (takip eder, iş yapmaz). ⚠ Ölçüldü: `ApproveQuote` bugün hiçbir görev açmıyordu ve onaylanan teklif kimsenin ekranında belirmiyordu.

⛔ **Onay eğitimi firmaya BAĞLAR — kapı buydu** *(EGT-140, Tayfun'un bulgusu; kararlar Sets, 14.08.2026)*. Ölçüldü: `ApproveQuote` koltuğu rezerve ediyor, kütüphaneyi açıyor, kanıtı yazıyordu ama `training_customers`'a hiçbir şey yazmıyordu — ve müşteri portalının eğitim listesi **yalnız o tabloyu** okuyor, katılımcı ekleme kutusu da o listedeki eğitimin sayfasında. Sonuç: teklif onaylanıyor, kimse eklenemiyor; kapalı eğitim 403, açık eğitim listede yok. Üç karar:

- **Onayda kendiliğinden bağlanır**, yalnız **kontenjanı teyit edilmiş** (`confirmed`) satırlar. `pending_confirmation` bir yer satmadı; firmayı olmayan koltuğu doldurmaya çağırmak olurdu.
- **Onaylı teklif ekranı sıradaki adımı söyler**: "Sırada: katılımcı listesi" kutusu + firma gündemindeki yaklaşan eğitim satırında kayıtlı kişi sayısı (sıfırsa iş olarak yazılır ve ekleme ekranına bağlar). Tasarım `11` bu sayıyı zaten çiziyordu, ekranda boştu.
- ⛔ **Ofis eli yazılmadı** *(Sets: "sahadaki duruma bakalım")*. Bugün ofiste katılımcı **ekleme** eylemi yok — yalnız öneriyi teyit ve yedekten çağırma var; liste tamamen müşterinin işi olarak planlanıyor. Haritadaki *"liste 24 saat kala kapanır, sonrası ofisten yapılır"* cümlesinin ofis yarısı **hâlâ yok** ve bilinçli olarak bekliyor.

⛔ **Kaç kişi sorusunun cevabı fiyat türüne göre değişir** *(Sets, 14.08.2026, EGT-140'ın ikinci yarısı)*. Ölçüldü: bugüne kadar **hiçbir türde sınır yoktu** — 5 kişilik teklif onaylayan firma eğitimin kontenjanı elverdiği sürece 20 kişi ekleyebiliyordu, günlük satırda ise kişi sayısı **hiçbir yerde saklanmıyordu** (miktar = GÜN).

| | Kişi başı fiyat | Günlük fiyat |
|---|---|---|
| Sayı nerede | `quote_items.quantity` — satılan yer sayısı | `quote_items.expected_participants` (teklifte donar) + `training_customers.expected_participants` (yaşayan sayı) |
| Firma için | **duvar** — fazlası reddedilir, ret cümlesi sayıyı söyler | **duvar değil** — yalnız beklenti; ekran «4 bekleniyordu, 7 eklendi» der |
| Kim değiştirir | değişmez; daha fazlası için yeni teklif | **satışçı ya da planlamacı**, eğitim ekranından (`setExpectedParticipants`) |

⛔ **Onaylı teklif hiçbir hâlde yeniden yazılmaz** (AD-14). Günlükte sayı değişince fiyat değişmediği için yeni sürüm/onay gerekmez; değişen sayı teklifte değil **eğitim–firma bağında** yaşar ve işlem günlüğüne kimin değiştirdiğiyle yazılır. ⚠ Bu, 05.08.2026'da kaldırılan `named_seats ≤ confirmed_seats` kısıtının geri gelmesi **değildir**: o kısıt fiyat türüne bakmadan her satıra uygulanıyordu ve tam da günlük satır yüzünden kaldırılmıştı; yeni duvar yalnız kişi başı satırda ve sebebini söyleyebilen bir aksiyonda.

⚠ **Ofis yolunda duvar yok** — sınır müşteri portalının ekleme akışındadır (`EnrolOwnParticipant`). Öneri teyidi ve yedekten çağırma ofisin bilinçli kararıdır ve satılan yerden fazlasına izin verir; eğitim ekranı sayıları yan yana gösterdiği için görünürdür.

⚠ İsimli katılımcı iki yolda farklı davranır ve ikisi de tasarımın kararı: **teklif isteğinde** (`14`) kişiler isimle seçilir ve öneri olarak eğitime taşınır; **açık eğitimde kontenjan isterken** (`17`) tasarım açıkça *"Kişi adı gerekmez — sayı yeterlidir"* der.

**Katılımcı yönetimi ayrı bir müşteri iznidir** *(Sets, 02.08.2026)*: `customer.manage_people` işe alım/çıkışın sorumlusudur (insan kaynakları), eğitim takibi ise eğitim koordinatörünündür. İki farklı görev, iki farklı izin.

**Liste, eğitim başlangıcından N saat önce kapanır**; N yönetici ayarıdır, varsayılan 24. ⛔ Süre koda gömülmez (kırmızı çizgi 9). Sonrasındaki değişiklik ofisten yapılır.

**İstekte verilen isimler öneri olarak durur**: müşterinin katılımcı ekranında "önerilenler" başlığı altında görünür ve **tek tıkla** katılımcıya dönüşür. ⛔ Kendiliğinden katılımcı olmazlar — teklif isteğindeki isim bir tahmindi, kontenjanı dolduran şey karar olmalıdır.

**Açık eğitim duyurusu: yetkiye ve frekansa bağlı** *(Sets, 02.08.2026)*. Ayrı bir izin (⛔ *"abuk subuk hevesli yeni yetme satışçılara verip müşterileri bunaltmamak lazım"* — arch-allow: turkish-content), hedef kitle seçilebilir (**seçili müşteriler / bölgedeki tüm müşteriler / yalnız o eğitim türünde süresi dolan personeli olanlar**), ve ⛔ **firma başına N günde bir** koruması (N yönetici ayarı, varsayılan 14). Duyuru firmanın bildirim alıcısına gider, herkese değil; aynı eğitim aynı firmaya iki kez duyurulmaz.

**Müşteri kendi bildirim kanallarını seçer** *(Sets, 02.08.2026)*: uygulama içi kalsın / e-postaya da gelsin ayarı müşteri tarafında da olmalı. ⚠ Altyapı hazır (`notification_preferences`: kişi × olay × kanal, zorunlu olaylar kapatılamaz); eksik olan portal ekranı.

**Mevcut eğitime bağlama** *(Sets, 02.08.2026)*: müşteri açık eğitime doğrudan ekler; kapalı eğitimde firması listedeyse yine ekler. Firması listede değilse planlamadan istenir, planlama uygun görürse firmayı kapalı gruba ekler — bu **teklif öncesi** bir planlama işidir.

**Ne yapar:** firma kendi organizasyonunu kurar, personel ekler, teklif ister, teklifi onaylar, eğitim geçmişini görür.

**Durum:** ofis tarafı müşteri yönetimi ✓ (EGT-62) · kimlik ✓ · giriş akışları ✓ · organizasyon/lokasyon ✓ (chunk 1) · katılımcı omurgası ✓ (chunk 2) · talep ve yazışma ✓ (chunk 3) · **teklif, indirim limiti, fatura, tahsilat, içerik paketleri ✓ (chunk 4)**

**Kabul kriterleri: 48/48 ✓** *(06.08.2026 kapsama matrisi, EGT-74 yorumu)*. İki kriter o turda kapandı:

- **M-43** — talep kapsamı plana çevrilirken **yalnız audit kaydına** yazılıyordu; `trainings`'te kapsam yoktu ve hiçbir satışçı listesi süzmüyordu. `training_scopes` açıldı, `PlanTrainingRequest` kapsamı **her** eğitime kopyalıyor, `TrainingsInScope` üç listede okuyor (teklif seçicisi, ofis eğitim listesi, satış kokpiti stoğu). ⚠ Kapsamsız eğitim herkesindir. ⛔ İki tuzak ölçüldü: `groupsFor` yetki ailesi bağlaması dışında **hata vermeden** "grup yok" der; **Staff** seviyesinin `group` kapsamı tasarım gereği `own`'a daralır.
- **M-37** — kod baştan doğruymuş (uyarı gövdede, gövde hash'leniyor, müşteri onaydan önce görüyor); eksik olan **gövde ile hash arasındaki bağı** kuran testti.

| Karar | Kaynak |
|---|---|
| Müşteri = tenant; iki hukuki tür (şirket / şahıs) | `omurga-spec.md` §3.1 |
| Kimlik tenant sınırını aşmaz — aynı kişi iki firmada iki ayrı kayıttır, eşleştirilmez | `omurga-spec.md` §3.2 |
| "Portal kullanıcısı" ve "katılımcı" ayrı hesap değil, **aynı kaydın iki yeteneği**; katılımcılık veriden türer, panel erişimi açıkça verilir | `omurga-spec.md` §3.0 |
| Müşteri kendi departman/grup yapısını kurar, gruplara yönetici atar | çatı §176 |
| Teklif isteğinde iç onay **müşterinin kendi belirlediği opsiyonel akış** | çatı §98, §176 |
| Teklif onayı panelden, scroll-to-accept ile | AD-14, `HANDOFF.md` |
| Müşteri silinmez, pasifleştirilir | `create_customer_identity.php` |
| Kişisel alanlar (e-posta, telefon, TCKN) tenant anahtarıyla şifreli; arama HMAC hash üzerinden | AD-05, chunk4 |
| Davet akışı ekranıyla birlikte gelecek — bilinçli erteleme | `omurga-spec.md`:724 (kriter 15a) |

**Veri:** `customers`, `customer_users`, `customer_user_sessions`, `customer_auth_settings`, `magic_links`, `customer_documents`, `consents`, `training_participants` ✓ · organizasyon/lokasyon ✓ (`customer_units`, `customer_unit_memberships`, `customer_locations`)

**Ekranlar:** `11` ✓ · `12` ✓ · `13` ✓ (liste + scroll-to-accept onay) · `14` ✓ (grup bazlı istek + iç onay kuyruğu) · `15` ✓ · `16` ✓ (sertifikalar, faturalar, içerik paketleri)

**`12`'nin üç eksiği kapandı** *(09.08.2026, EGT-73 ölçümü)*. Zincir yazılmıştı, zincirin **başındaki ekran yazılıp bırakılmıştı**:

- ⛔ **Satır menüsü (`⋮`) yoktu ve beş uç noktanın çağıranı yoktu.** `InvitePortalPerson`, `SetPersonUnits`, `SetPersonLocations`, `UploadParticipantDocument`, `ChangePortalPersonStatus` — hepsinin `routes/panel.php`'de adresi vardı, ön yüzde ters grep sıfır sonuç veriyordu. Sonuç: personel **bir kez eklenip bir daha düzenlenemiyordu** — panele davet edilemiyor, grubu/lokasyonu değişemiyor, eksik evrağı sonradan yüklenemiyor, işten ayrılan pasifleştirilemiyordu. `resources/js/pages/Portal/People.vue:486` (`row-action` → `RowMenu`).
- ⛔ **Uyum şeridi yoktu.** Tasarımın beş rozeti (Tümü · Uyumlu · Yaklaşan · Geçmiş · Bekleyen, sayılarıyla) hiç çizilmemişti; süzme yalnız **gruba** göreydi. Sayılar **şirketin tamamı** üzerinden alınır, sayfadan değil — ve durum seçilince toplam yerinde kalır. Durum bir kolon değil (`ComplianceState` iki ayara ve iki tarihe bakar), o yüzden SQL'de değil **kimlik listesiyle** daraltılır: aynı kural iki kez yazılsaydı liste kendi şeridiyle çelişebilirdi. `MyPortalPeople.php` + `People.vue:104`.
- ⚠ **Sertifikanın kapsadığı görevler yazılmıyordu** (YON-2019 md.7/1). Kayıt zaten `certificate_records.covered_scopes`'ta donmuş duruyordu, liste okumuyordu; bu satır olmadan zaten sahip olunan görev için yenileme planlanabiliyordu.

**Dördüncü eksik: kişinin kendisi düzeltilemiyordu** *(10.08.2026, EGT-98 — Tayfun: "personel kaydı yaptım hatalı yaptım düzenleyemiyorum")*. EGT-73 satır menüsünü getirdi ama beş kalemi de kişinin **yerine** dairdi: grubu, lokasyonu, evrağı, daveti, durumu. Adı yanlış yazılmış birini düzeltmenin yolu **kaydı pasifleştirip kişiyi ikinci kez girmekti** — yani her yazım hatası firmanın listesinde iki satır bırakıyordu.

- **Satır menüsünün ilk kalemi "Bilgileri düzenle"** → `PUT /personel/{person}` → `UpdatePortalPerson`. Toplanan alanlar tasarım `12`'nin drawer'ıyla aynı ve **yalnız kimlik alanları**: ad, TCKN, telefon, e-posta, işe giriş *(Sets, 10.08.2026 — grup ve lokasyon kendi kalemlerinde kalır, aynı olguya ikinci yazıcı açılmaz)*.
- ⛔ **TCKN kutusu boş açılır**, çünkü liste onu yalnız maskeli tutar (AD-16). Boş kutu **"dokunma"** demektir; kutunun altındaki not kayıtlı maskeyi gösterir. Aksi hâlde telefonunu düzeltmeye gelen kişi, hiç görmediği numarayı silmiş olurdu.
- ⛔ **E-posta bir eşikten sonra kilitlenir** *(Sets, 10.08.2026)*: davet gönderildiyse, şifre varsa ya da adres doğrulanmışsa artık **giriş kimliğidir** ve listeden taşınmaz — alan **gösterilir ama pasiftir**, sebebi altında yazar; sunucu da reddeder (`CustomerUser::usesEmailToSignIn`, liste sayfa başına tek sorguyla `can_change_email` üretir). Davet gitmemiş kişide serbestçe düzeltilir; o hâlde adres henüz "yazım hatası olan iletişim bilgisi"dir.
- Değişiklik `portal_person.updated` olarak **eski ve yeni değeriyle** audit'lenir (AD-06; hassas alanlar `Masker` ile maskeli). Değişmeyen alan yazılmaz — dokunulmamış drawer audit satırı üretmez.

**Beşinci eksik: bir kişinin birden fazla sertifikası olabilir** *(10.08.2026, EGT-97 — Tayfun'un personel ekleme eleştirisi; kararlar Sets, aynı gün)*. Bugünkü `certificate_records` iki şeyi birden varsayıyor ve ikisi de yanlış: sertifika **bizim verdiğimiz bir eğitimin kaydından doğar** (`training_enrolment_id` NOT NULL, kayıt başına tekil) ve **kişi başına tektir** — liste sorgusu `keyBy('customer_user_id')` yaptığı için ikinci sertifika sessizce düşüyor (`MyPortalPeople.php:245`). Sonuç demo veride görünüyor: elinde geçerli belgesi olan kişilere ekran "süresi geçmiş" diyor.

✅ **Omurga yazıldı (10.08.2026).** `certificate_records`: `training_enrolment_id` nullable, yanında `training_definition_id`, `customer_user_id` (beyan edilenin sahibi — bizimkilerde sahip kayıttan gelir), `source` ve `declared_by_customer_user_id`. Enrolment tekilliği **kısmi indekse** çevrildi (`WHERE training_enrolment_id IS NOT NULL`) ve iki CHECK eklendi: satır ya kayda ya tanıma bağlı, ya kayda ya kişiye. Liste sorgusu artık `keyBy` değil **`groupBy`** ve LEFT JOIN — beyan edilen satırlar INNER JOIN'de tamamen düşüyordu. Kişi rozeti `personState()` ile en kötü sertifikadan türüyor; sertifikası olmayan kişide işe giriş kuralı cevap veriyor. Ekran: satır menüsünde **Sertifikalar** → drawer'da her belge kendi rozeti/kaynağıyla + ekleme formu; şeridin altında belge özeti. `DeclareStaffCertificate` + `certificate.declared` audit'i. ⚠ Süresiz belge (bitiş tarihi boş) **uyumlu** sayılır — tarih uydurmak kişiyi ait olmadığı yenileme kuyruğuna sokardı.

- **Sertifika ekle** akışı gelir: firma, **bizde kayıtlı eğitim tanımlarından** seçer, **sertifika kodunu ve geçerlilik tarihini** girer. Böylece 3E'nin vermediği eski belgeler de yenileme takibine girer. ⚠ Mevzuat kaydıyla çelişmiyor: *"sertifikayı bakanlık üretir, biz numara + veriliş + geçerliliği saklarız"* — beyan edilen sertifika da tam olarak bu üç olgu; enrolment sertifikanın kaynağı değil, **onu nasıl öğrendiğimizdir**. O yüzden `training_enrolment_id` nullable'a çekilir, yanına eğitim tanımı ve kaynak yazılır; ikinci bir tablo açılmaz.
- ⛔ **Beyan uyumu BESLER, ama ekranda işaretlidir** *(Sets)*: yanında "firma beyanı" rozeti, kimin ne zaman girdiği kayıtlı; bizim eğitimimizden doğan sertifika doğrulanmış sayılır. Beslememek, elinde belgesi olan kişiye "süresi geçmiş" demeye devam etmek olurdu.
- ⛔ **Durum artık kişinin değil BELGENİN** *(Sets)*: her sertifika kendi tarihi ve kendi durumuyla, kişi satırı açıldığında tek tek görünür.
- ⛔ **Şerit ve dashboard yine KİŞİ sayar** *(Sets)* — kişinin durumu en kötü sertifikasından türer (geçmiş > yaklaşıyor > uyumlu), hiç sertifikası yoksa "eğitim bekliyor". Altına ikinci bir satır belge özetini yazar (*"toplam 34 sertifika · 5'i 90 gün içinde doluyor"*). ⚠ Sebep: "eğitim bekliyor" **sayılabilir bir belge değildir** — sertifikası olmayan kişinin durumudur; şerit belge saysaydı o pill'in sayacağı hiçbir şey olmazdı ve tek şeritte iki farklı birim toplanırdı. Uyum kartı ve satıştaki "süresi dolan personeli olan firmalar" sorgusu da kişi bazında kalır.
✅ **Grup → Departman yapıldı (10.08.2026).** Yalnız müşteri birimleri (`customer_units`): organizasyon ekranı, personel listesi kolonu ve süzgeci, import şablonu başlığı, tr/en. ⚠ **Karışmaması gerekenler dokunulmadı:** 3E'nin kendi grup ağacı (ofis/backoffice) ve teklif isteğindeki **katılımcı grubu** — o bir departman değil, bir eğitim için toplanan kişi kümesidir. Adres parametresi `?departman=` oldu, `?grup=` hâlâ okunuyor (kayıtlı bağlantılar kırılmasın).

✅ **Personel ekleme TAM SAYFA oldu** *(Tayfun'un isteği, Sets onayı 10.08.2026 — tasarım `12`'den bilinçli sapma)*. `/personel/yeni`; alanlar ve sıraları drawer'ın aynısı, taşınan yalnız yüzey. Sebep: form sertifika satırlarını da toplamaya başlayınca 600px'lik drawer kendi kaydet düğmesinin altına kayıyordu. ⚠ Evrak ve sertifikalar kayıt **oluştuktan sonra** birer istekle gidiyor — belge birine ait olur, kişi yoksa sahibi yok.

✅ **Zorunlu evrak artık BLOKLAMIYOR** *(Tayfun)*. Kritik evrak yüklenmeden kayıt tamamlanabiliyor; eksik olan drawer'da amber uyarı, listede kırmızı hücre olarak duruyor. Hangi evrakın kritik olduğu yine admin tanımlı (AD-08). Sebep: kaydı açan kişinin elinde o an evrakların hepsi olmuyor ve açılmayan kayıt için eğitim planlanamıyor.

✅ **Davet akışının eksik iki parçası kondu** *(EGT-97)*: yetki matrisinin başlığına tasarım `11`'in çizdiği **"+ Kullanıcı davet et"** bağlantısı geldi; matris satırları artık **nerede durduklarını** söylüyor — *davet edildi, henüz girmedi* / *yetki bekliyor* — ve yetki bekleyenler matrisin üstünde ayrı bir şeritte toplanıyor. İkisi de var olan olgulardan okunuyor (`magic_links.purpose='invitation'`, `password`), tek sorguyla.

✅ **Lokasyon: çoklu kalır, etiket netleşir** *(Sets, 10.08.2026)*. Tayfun tekil yazmıştı; model (`customer_user_locations` pivot) ve tasarım `12` çoklu. Etiket **"Çalıştığı yerler"**, notu *"bir kişi birden fazla yerde çalışıyorsa hepsini seçin"*. ⚠ Tekile indirmek kayıtlı ikinci lokasyonları atmak olurdu — bir personelin iki limanda çalışması mümkün.

- **Grup → Departman** *(Sets: "çok büyük bir dert değil, planlı yapılır")*. Yüzey yalnız arayüz: `lang/tr/portal.php` (~40 anahtar) + ofis ekranları + ilgili yardım makaleleri; tablo adı `customer_units` İngilizce kaldığı için kod etkilenmez.

**Cep telefonu doğrulaması — ara çözüm** *(Sets, 10.08.2026, EGT-97)*. 3E'nin müşteriye gönderdiği Excel formu *"e-posta kullanıcı adı, başında 0 olmadan 10 haneli cep telefonu ilk geçici şifredir"* diyor ve müşteri ısrarla telefon doğrulaması istiyor. ⛔ **Telefon numarası şifre olmaz.** Kararlaştırılan hâl: davette **hem e-postaya bağlantı, hem cep telefonuna kod** gider; ikisi de yalnız **doğrulamadır** — kimlik kanıtıdır, kimlik bilgisi değil. **Şifresini kullanıcı ilk girişte kendisi oluşturur.**

⚠ **Bugün yapılabilecek bir şey yok ve sebebi tek satır:** SMS sağlayıcısı seçilmedi. Adaptör hazır (`App\Integrations\Sms\SmsGateway`), bağlı olan `NullSmsGateway` ve o kendini **kullanılamaz** ilan ediyor — sağlayıcı seçildiği gün iş, bu dizine tek bir sınıf yazmak (AD-12; sağlayıcıyı bellekten yazmak kırmızı çizgi 3).

⛔ **Davetli kişi panele hiç giremiyordu — kapının iki yanında da kol yoktu** *(tarayıcıda ölçüldü, 10.08.2026)*. Zincir şöyleydi: davetli kişinin **şifresi yok** (davet edilmek bu demek), tek kapısı posta kutusundaki bağlantı, ve magic link oturumu **bilerek zayıf** — `RequireStrongAuth` panel işine 403 der, çünkü bağlantı paylaşılabilir. Ama ilk tur ekranlarının **ikisi de** o kapının arkasındaydı: kullanım koşuluna 403, panele 403, şifrenin belirlendiği ekrana 403. Zayıf oturumun ilacı şifreydi ve şifre, zayıf oturumun geçemediği kapının arkasındaydı. **Hiç kimsenin fark etmemesinin sebebi:** demo hesapların hepsi şifreyle seed ediliyor, yani bu yoldan hiç geçilmemişti.

- **`RequireStrongAuthUnlessFirstRun`**: sözleşme ve şifre ekranları, kişinin **şifresi yokken** zayıf oturumla açılır. İstisnanın sınırı tarayıcıdan uydurulamayacak bir olgu: şifre var mı yok mu. Şifre oluştuğu an kural eskisi gibi işler ve **geri dönüş yok** — şifre hiç geri alınmaz.
- **`RequireCustomerPassword`** paneli şifresiz kişide `/sifre/belirle`'ye yönlendirir; ⚠ **sıra yük taşır**: sözleşme → şifre → güçlü kimlik. Güçlü kimlik önce koşarsa zayıf oturum 403 alır ve onu düzeltecek ekrana götüren yönlendirme hiç çalışmaz.
- ⛔ **Şifre belirlendiğinde oturum KAPANIR, yükseltilmez.** Kişi paylaşılabilir bir bağlantının oturumunda duruyor; şifre seçme eyleminin oturumu sessizce güçlendirmesi, iletilmiş bir bağlantının tam panel erişimiyle sonuçlanması demekti — `RequireStrongAuth`'un var olma sebebinin tam tersi. Döngü dürüst yoldan kapanır: şifre yazılır, oturum kapanır, giriş ekranı o şifreyi sorar.

**TCKN zorunlu ve kendi aritmetiğiyle doğrulanır** *(Sets, 10.08.2026 — ⚠ bir yanlış çıkarımın düzeltmesi: ticket'taki yıldız dağılımından "zorunlu değil" sonucu çıkarılmıştı, öyle bir talep yok)*. `App\Domain\Shared\NationalIdentityNumber` (çerçevesiz) + `WellFormedNationalId` kuralı + `lib/identity.js` — biri sunucunun, biri tarayıcının yarısı, **tek tanım**. On bir hane bir *şekildir*, doğrulama değil: numara iki kontrol hanesi taşır, tam da yanlış yazılanın yazıldığı yerde yakalanabilsin diye. Bu alandan aşağısı buna bağlı — magic link kimlik teyidi son dört haneyi okur, bakanlık bildirimi bu numarayı taşır, sertifika buna kesilir; yanlış bir hane haftalar sonra "giriş yapamayan kişi" olarak ortaya çıkar. Portal personel eklemede **zorunlu**, düzeltme drawer'ında boş kutu "dokunma" demek olduğu için **nullable ama yazılan doğrulanır**; ofis tarafındaki üç `digits:11` kuralı da aynı kurala çevrildi.

📄 **Formun düzeltilecek cümleleri yazıldı:** `docs/musteri/bilgi-formu-metni.md` — geçici şifre cümlesi, eksik TCKN sütunu, "il/görev" yerine lokasyon/departman, sertifika için numara + eğitim sütunları, "firma başına 2 yetkili" okuması. Tayfun'a iletilecek; Excel 3E'nin elinde.

⚠ **Ama formdaki cümle bugün de yanlış:** biz hiç geçici şifre üretmiyoruz ve davet ekranı şifre kurdurmuyor — davet bağlantısı "kabul edildi" deyip giriş ekranına gönderiyor (`Portal/Auth/Invitation.vue`), kişinin şifresi `null` kalıyor ve içeri ancak "bana giriş bağlantısı gönder" ile giriyor. Yani **Sets'in tarif ettiği akışın şifre adımı da eksik**; SMS'ten bağımsız ve şimdi kapatılabilir.

✅ **EGT-79 (D-16) — firmadan beklenen belge, UYARI olarak** *(Sets, 11.08.2026: "uyarı çıkartırız, belge yükleyebilir ama yüklemezse de uyarıyı görerek teklif isteyebilir")*. Ticket'ın üç eksiğinden ikisi kapandı, üçüncüsü zaten bugünkü işlerle cevaplanmıştı:

- **Eğitim tanımı ↔ müşteri belgesi bağı** yazıldı: `training_definition_customer_document` (eşleşme + **madde referansı** + not). ⛔ Belge adı kodda yok; hangi belgenin beklendiği backoffice'te eğitim tanımında işaretlenir (kırmızı çizgi 14). Madde referansı **eşleşmenin** üstünde durur, belgenin değil — aynı belge iki eğitimde iki ayrı maddeye dayanabilir.
- **Tüketen taraf**: teklif isteğinin özet adımında `MissingCompanyPapers` — hangi belge, hangi eğitim için, hangi madde; "hiç yüklenmemiş" ile "süresi dolmuş" ayrı cümleler (iki farklı çekmece). ⛔ Gönder düğmesine dokunmuyor.
- "Zorunluluk kavramı yok" maddesi artık boşluk değil **karar**: bugün EGT-97'de zorunlu evrak bloklaması da kaldırıldı — ürünün cevabı üç yerde aynı: **uyarır, zorlamaz** (AD-27).
- ⚠ Ölçümde çıkan dördüncü şey duruyor: sistemde `musteri_firma` kapsamında **tanımlı belge türü yoktu**. Yapı hazır; "Gözetim yetki belgesi" türünü backoffice'ten açmak yönetici işi.

✅ **EGT-73'ün son bağlanmamış halkası kapandı** *(11.08.2026)*. Zincir ölçüldü: personel/lokasyon → katılımcı → yoklama/sınav → günü kapat → çelişki/tamamlama → hakediş → cari, **hepsinin çağıranı var** (hakediş `FinishTraining` içinde doğuyor ve aynı işlemde `SettleInstructorEarning` ile cariye geçiyor). Tek istisna: **adı konmuş yedeğin koltuğa geçmesi** — dolu eğitime yazılan kişi `reserve` olarak açılıyor, portal bunu gösteriyor, ama `PromoteReserveEnrolment`'ı hiçbir şey çağırmıyordu.

- `POST /egitimler/{t}/katilimcilar/{enrolment}/cagir`; eğitim ekranındaki katılımcı listesinde **"Yedekten çağır"**, gerekçe zorunlu (kayda işlenir).
- ⛔ **Yetki satışın** *(Sets, 11.08.2026: "planlamacının tek işi planlamayı yapmak, sonrası satışçının işi")*. Yeni izin **`training.manage_participants`**, iki satış paketinde varsayılan; `training.update` da kabul eder (bütünü değiştirebilen küçüğü de yapar). ⚠ `training.update`'e gömülmedi: o hakkı satışa vermek eğitimin kendisini düzenleme hakkını vermek olurdu.
- ⚠ Karıştırılmaması gereken iki liste: `/egitimler/{t}/yedek/{entry}/cagir` **firma koltuğu** (isimsiz, teklif akışından), bu ise **adı konmuş kişi**.

⛔ **Sistemin çözdüğü soru ekranda yazmıyordu** *(EGT-103, Tayfun; kararlar Sets, 11.08.2026)*. Teklif isteğinin ilk adımı *"sertifikası dolan 7 kişi"* diyor, **kimin hangi sertifikası** dolduğunu söylemiyordu; devam edilince eğitim seçimi de boş geliyordu. Tayfun'un cümlesi: *"burda ceva yöneticisi olarak kafam karıştı… körüm şu an bir şey göremiyorum."*

Kök neden ekran değil **sorgu**: `RequestStartingPoints` kendi okumasını yazmıştı ve iki yerde yanılıyordu — (a) `max(valid_until)` **kişi başına** tek satır üretiyordu, yani ADR'si bir ay sonra dolan ama IMDG'si üç yıl geçerli olan personel "uyumlu" sayılıp **hiç listeye girmiyordu** (görüntü eksiği değil, yanlış tespit); (b) join yalnız `training_enrolments` üzerindendi, EGT-97'de gelen **firma beyanlı sertifikalar** bu ekranda hiç okunmuyordu. Personel ekranı ikisini de doğru okuyordu — **iki ekran aynı olguyu iki farklı sorguyla cevaplıyordu.**

- Okuma tek yere alındı: `App\Queries\Customer\HeldCertificates` (kişi başına gruplu, `definition_id` dahil); `MyPortalPeople` ve `RequestStartingPoints` ikisi de onu kullanıyor.
- **Tespit birimi artık kişi değil SERTİFİKA.** İlk adımda satır listesi: kim · hangi eğitim · bitiş · durum, işaretli gelir, çıkarılabilir. Chip'ler kişi sayar, liste sertifika sayar — bir kişinin iki dolan belgesi bir isim, iki satır.
- **Devam → eğitim başına ayrı grup**, eğitimi seçili (tasarım `38`'in kendi cümlesi: "hazır **gruplara** dönüşür"). İşe giriş kuralına girenler tek grupta, **eğitimi boş**: hangi eğitime ihtiyacı olduğunu hiçbir kayıt söylemiyor, uydurmak bizim işimiz değil.
- Ekibim › Personel: **"Sertifika eğitimi" süzgeci** (rozet ve şerit de o sertifikaya göre okunur — IMDG'si geçerli, ADR'si geçmiş kişi ADR süzgecinde "Süresi Geçmiş"), **"Sertifika bitiş" sıralaması** (tarihsiz olan sona), **satır seçimi + "Eğitim planına ekle"** → teklif isteği adlar yazılı açılır; liste tek eğitime süzülüyse eğitim de seçili gelir.
- ✅ **Yol üstünde bulunan ve aynı ticket'ta düzeltilen** *(Sets: "hatayı bulmuşsun, düzelt ve notunu yaz")*: `DataTable`'ın filtre popover'ı **yalnız kendi düğmesiyle** kapanıyordu ve açıkken tablonun üstünde duruyordu — opak, ve en uzun seçenek listesi kadar yüksek. Yani filtre seçen kişi listenin **boşaldığını** sanıyordu; satırlar sayfadaydı, görünmüyordu. `RowMenu`'nun deseni uygulandı (dışarı tıklama + Esc, `data-filter-popover`), değer listesine `max-h-80` + kaydırma kondu. ⚠ Bugüne dek fark edilmeme sebebi ölçüldü: bu kontrolü taşıyan tek ekranda **tek boyut ve birkaç departman** vardı, panel tablonun yanına sığıyordu; 14 eğitimlik liste onu duvara çevirdi.

⛔ **Bir eğitim tek mevzuata sıkışmıştı** *(EGT-101, Tayfun: "imdg kat 4 ve 10 hem imdg kod yönergesine hem de tehlikeli yüklere ilişkin yönergeye tabi… her ikisini de seçebilmeliyiz")*. Katalog tam olarak birine izin veriyordu; ikinci yönergenin kartından arayan planlamacı eğitimi **bulamıyordu** — o kart açısından eğitim yoktu.

- `training_definition_regulations` (pivot + **madde referansı**). ⛔ `regulation_id` **asıl olarak kalıyor** *(Sets'in kararı)* ve bu bir modelleme tavizi değil: `trainings.regulation_version_id` planlama anında donuyor — bakanlık bildiriminin hangi yönergenin hangi sürümüne göre yapıldığı delildir ve iki zincire birden işaret eden bir kayıt hangi sürüm altında beyan edildiğini söyleyemez.
- Ekranda: `TrainingDefinitionOptions` artık `regulation_ids` (asıl önce) taşıyor, planlama filtresi `includes()` ile bakıyor, kart sayıları ikinci bağı da sayıyor (yoksa "0 tanım" yazan kartın altında eğitim listelenirdi).
- Backoffice'te "Ayrıca tabi olduğu yönergeler" çoklu seçim + madde referansı.
- ⚠ Yeni tablo `config/tenancy.php`'de **paylaşılan** olarak bildirildi ve `TenancyTest`'in sayısı güncellendi — katalog verisi hiçbir tenant'a ait değil.

⛔ **Yenileme eğitimi yeni eğitim gibi planlanıp fiyatlanıyordu** *(EGT-101, Tayfun: "yeni 16 yenileme 8 saat gibi ayrımları planlama sayfasında görmeliyiz ve seçebilmeliyiz")*. Eğitim tanımı **iki saati de** taşıyor (`initial_hours`, `renewal_hours`) ve `trainings.kind` kolonu **04.08.2026'da bilinçle** eklenmiş — `first|renewal`, nullable, *"uyarır, reddetmez"* kararıyla. Ama kolon `fillable` değildi ve hiçbir yerde yazılmıyordu:

- `ScheduleSessions` ve `PriceForTraining` **her zaman `initial_hours`** okuyordu → 8 saatlik yenileme iki güne bölünüyor, saatlik fiyatta iki katına çıkıyordu (testle ölçüldü: yenileme 1 oturum, yeni eğitim 2).
- ⛔ **Ve `TrainingRoster`'daki yenileme uyarısı ölü koddu**: `$training->kind === 'renewal'` karşılaştırması vardı, kolon hiç yazılmadığı için her zaman `false`. Yani YÖN-2019 md.8'in ürün karşılığı (sertifikası dolmuş kişiyi işaretleme) yazılmış ve hiç çalışmamıştı. Planlama formundaki seçim onu da ayağa kaldırdı.
- `TrainingKind` enum'u tabloya uydu (`first`, `initial` değil — CHECK constraint zaten o iki kelimeyi biliyordu). Eski satırlar `courseKind()` ile **yeni eğitim** sayılır: sistem onları zaten öyle hesapladı, başka türlü okumak faturalanmış geçmişi değiştirmek olurdu.
- ⚠ Ölçülen ders: `kind` kolonunun **varlığını görmeden** migration yazdım ve Postgres "duplicate column" ile durdurdu. Tabloyu okumak, migration yazmadan önce gelir.

⛔ **Masraf planlama formunda yoktu** *(EGT-101, Sets 11.08.2026: "masrafların eklenebileceği yeri konuştuk ama planlama formunda bunlar yok")*. Tablo (`training_cost_items`), model, action (`AddCostItem`) ve detay ekranı **hazırdı**; eksik olan tek şey planlama formunun onları hiç sormamasıydı. Tasarım `21` maliyeti sihirbazın 4. adımı olarak çiziyor ve sebebi sıradır: salon, otel ve ulaşım tarihlerle **aynı konuşmada** belli olur — kaydedip detay ekranına dönmek, sonradan hafızadan girmek demektir.

- Kalemler **eğitim satırının içinde** (kayıt `training_id`'ye bağlı). `NewTraining.costs` → `PlanTrainings::recordPlannedCosts()` → `AddCostItem`. ⛔ Doğrudan tabloya yazılmadı: action yetki kontrolünü ve audit satırını taşıyor, planlanan tutar da teklifin kurulduğu rakam — "bunu kim yazdı" sorulan bir sorudur.
- ⚠ Paket uyarısı ekranda: iki eğitimin paylaştığı gider `training_package_cost_items`'a aittir; iki satıra da yazılırsa her raporda iki kez sayılır.
- ⚠ **Ekran kendi kendisiyle çelişiyordu**: sağdaki "BU SAYFADA YOK" kutusu maliyet kalemlerini sayıyordu. Cümle güncellendi — yoksa okuyan kişi iki satır yukarıda duran alanı aramaya giderdi.
- ⚠ İki sessiz kusur yol üstünde yakalandı: `SelectInput` import edilmemişti (tür seçici hiç çizilmiyordu, hata da vermiyordu) ve şablonda `form.trainings` yazılmıştı — o değişken yok, ekran konsola düşüyordu. İkisi de tarayıcıda görüldü, testte değil.

⛔ **Panel bildirim ekranı 500: politika yanlış kimlik alanına soruluyordu** *(EGT-102, Tayfun; 11.08.2026)*. Bildirim satırını bağlantıya çevirirken konu kaydının politikasına `view` soruluyor. Panelin haber alabildiği dört kayıttan üçünün politikası `Authenticatable` tipinde — teklif, teklif isteği, fatura — ve **iki kimlik alanını da** yanıtlıyor; `TrainingPolicy` ise `User` istiyor. Firma kullanıcısı geçince PHP çağrıyı reddetti: **TypeError, yani "hayır" değil 500**. Yani arıza yetkiyle ilgili değildi; ekran çizilemiyordu.

- Düzeltme sınıf düzeyinde: `mayOpen()` artık politikanın `view` imzasını okuyor ve **o okuyucuyu kabul etmiyorsa hiç sormuyor**. Elle tutulan "hangi politika hangi alana bakar" listesi ikinci bir hatırlama yeri olurdu — EGT-102 tam olarak öyle üretildi.
- ⚠ Yanıtlanamayan durum **açılabilir** sayılıyor ve bu bir boşluk değil: satır zaten o kişiye teslim edilmiş, kayıt zaten kendi tenant kapsamında okunmuş, ve hedef ekran kendi kapısını koruyor (`Customer\TrainingController::show` → `reachable() && bought()`, aksi hâlde 404).
- ✅ **İkinci kusur da kapandı** *(Sets'in kararı, 11.08.2026: "404 yerine açıklama göster")*. Erişilemeyen eğitim artık çıplak 404 değil, **403 + sebebi yazan cümle** veriyor: kaydın yok, firman satın almamış, yanlışsa 3E'ye yaz. ⚠ Sebebi söylemek burada sızıntı değil: adres tahmin edilemeyen opak token taşıyor, yani oraya ulaşan kişi zaten bizim bir ekranımız tarafından yönlendirilmiş. Gerekçe `abort(403, …)` mesajından **merkezî hata katmanında** okunuyor (`bootstrap/app.php`), yani her ekran kendi cümlesini söyleyebilir; Symfony'nin kendi "Forbidden" metni ayıklanıyor. Aynı cümle yazdırılabilir katılımcı listesinde de geçerli — tek kayıt, iki kapı, tek cevap.

⛔ **Audit tanığında yarış: iki iş aynı klonda** *(EGT-104, Sets 11.08.2026: "böyle bir yapıda race condition kabul edilemez")*. Prod logunda ölçüldü — 08:00–10:00 arası altı başarısız push (`cannot lock ref`, sonra `non-fast-forward`). Ama **loglanan yarı, tehlikeli olan yarı değildi**: tanığın sırası `fetch → reset --hard → append → commit → push` ve bu tek bir klonda koşuyor, yani ikinci çağıran birincinin **henüz push edilmemiş commit'ini siliyor** — mühür sessizce kayboluyor, tek iz `witness_reference`'ı boş kalan zincir halkası.

Eşzamanlılık tasarım gereğiydi: `AuditServiceProvider` her kanıt tablosu için **ayrı** bir `SealAuditBatch` planlıyor ve `withoutOverlapping()` **event'in kendi adına** kilitleniyor — `audit:seal:audit_writes` ile `audit:seal:audit_access` iki ayrı ad, ikisi de :00'da ateşleniyor.

İki katman kondu, çünkü iki ayrı arızaya cevap veriyorlar:
- **Seri kuyruk** (`audit-witness`, kendi süpervizörü, **her ortamda tek işçi**) — çarpışmanın olmasını engeller.
- **Tanığın kendi kilidi** (`GitAuditWitness::exclusively`) — olduğunda zararsız kılar. ⛔ Kilit **job'da değil tanıkta**, çünkü kuyruk tek kapı değil: `VerifySingleSeal` ekrandan doğrudan `published()`'a giriyor ve o yol da fetch+reset yapıyor.
- ⚠ Kilit beklemesi `Lock::block()` ile **yazılamadı**: `block()` sabrını `currentTime()` ile ölçüyor, o da Carbon okuyor ve **testlerde saat donuk** (AD-26) — donmuş saat hiçbir zaman süreyi geçmiyor, ilk çakışan test süite öldürülene kadar asılı kaldı. Bekleme `hrtime()` (monotonik) ile yazıldı.

⛔ **Deploy, ticket teslimin parçasıdır** *(Sets, 11.08.2026)*. **Tayfun yalnız production'ı görür.** Deploy edilmemiş bir işi ona atamak, test edemeyeceği bir şeyi test etmesini istemektir — ekranı arar, bulamaz, "yapılmamış" diye geri döner. Sıra sabit: **yeşil CI → deploy → canlıda doğrula → `In Review` + Tayfun.** Deploy edilemiyorsa ticket atanmaz, sebebiyle Sets'e söylenir.

⛔ **Ekrandaki hata kodu logda da yazar** *(Sets, 11.08.2026 — başka bir projenin dersi: "500 gibi hatalarda çıkan ekranda bir kod üretiyorduk ama bu kod loglara yazmıyordu ve 500 veren hataların ne olduğunu çözemiyorduk")*. Bizde aynı arızanın öteki yarısı vardı: ekran *"Hata kaydedildi"* diyor — kayıt YAPILDI — ve alıntılanacak hiçbir şey basmıyordu; tasarım `10` ise o cümlenin yanına kodu çiziyor. `App\Support\Errors\ErrorReference` (istek başına **scoped**, tembel üretilir) hem `$exceptions->context()` ile **her raporlanan istisnanın log satırına** girer hem ekrana basılır. ⚠ Kod yalnız **500**'de gösterilir: 403/404 birer cevaptır, yanlarında kod olması araştırılacak bir şey varmış gibi okunur. ⚠ Gösterim `wasMinted()`'e bağlı — istisna raporlanmadan 500'e dönmüş bir yanıtta bağlanacak log satırı yoktur ve kod basmak olmayan bir kaydı vaat eder.

⛔ **Hata sayfalarında logo yoktu** *(Sets, 11.08.2026: "sayfalarda logomuz olmak zorunda")*. Hata ekranı, kabuğu olmadan karşılaşılan birkaç sayfadan biri — ray yok, başlık yok, bunun kimin sistemi olduğunu söyleyen hiçbir şey yok. Markasız hâliyle "internetin hatası" gibi okunuyor, yani kişi *bir şey ters gitti* değil *site bozuk* diye düşünüyor. ⚠ Bakım sayfasında logo **gömülü** basılır (`file_get_contents` + inline SVG): o sayfa uygulama kapalıyken düz dosyadan servis edilir; nginx `/images/…`'i büyük ihtimalle yine verir ama deploy anında görülen tek sayfa "büyük ihtimal"e asılmaz.

⛔ **Bakım süresi ekranda yazar** *(Sets, 11.08.2026)*. ⚠ Laravel'de bakım **penceresi yoktur** (kaynaktan doğrulandı, `DownCommand`) — ama `--retry` saniye **ya da tarih/saat** alır ve önceden çizilen görünüme `$retryAfter` olarak geçirir; **hiçbir şey okumuyordu**, yani bekleyenin istediği tek bilgi toplanıp atılıyordu. `App\Support\Errors\MaintenanceWindow` onu "Tahmini bitiş: 18:30"a çevirir. ⚠ Yazılan saat **display timezone**'da okunur — `--retry="21:30"` diyen kişi kendi saatini kastediyor; UTC sayılsa sayfa geçmiş bir saati vaat ederdi. ⚠ Bir dakikanın altı yazılmaz (okunana kadar geçer, sistem takılmış gibi görünür), okunamayan değerde satır çıkmaz. Deploy'un değeri 15sn → **300** (`MAINTENANCE_RETRY` ile değiştirilebilir).

⛔ **Tanıtım alanının hiç hata sayfası yoktu** *(ölçüldü, 11.08.2026)*. Panel ve ofis kendi ekranlarını taşıyor; **public host** (landing + yasal metinler) çerçevenin İngilizce, markasız "Server Error" sayfasını veriyordu — yabancının geldiği tek yer. `resources/views/errors/` altında ortak bir iskelet (`shell.blade.php`) ve üç sayfa: **500** (kodla), **404**, **503**. ⚠ Blade, çünkü landing bir view'dur ve bakım sayfası uygulama koşmazken basılır.

⚠ **500 ekranı görülebildiği hiçbir yerde açılamıyordu**: koşul `local`/`testing` dışıydı, yani prod dışında asla çizilmiyordu — bir yabancı tarafından okunmak için yapılmış tek ekran, kimsenin bakamadığı ekrandı. Ölçüt **`config('app.debug')`** oldu: debug'ı kapatan kişi production davranışını istiyordur.

✅ **Bakım sayfası yazıldı** *(Sets, 11.08.2026: "düzgün bir maintenance sayfamız var mı emin olalım")*. Yoktu: `artisan down` Laravel'in kendi 503'ünü basıyordu — bomboş beyaz ekranda İngilizce "503 Service Unavailable". Tasarım `10`'un "Planlı bakım" kartı tam sayfaya uyarlandı (`resources/views/errors/503.blade.php` + `lang/*/errors.php`); deploy artık `--render=errors.503` diyor, `errors::503` değil. ⛔ **Hiçbir dış kaynak yok** — stil satır içinde, renkler token değerleriyle birebir: sayfa tam olarak uygulamanın çalışmadığı anda gösteriliyor, Vite manifesti okunmuyor ve derlenmiş CSS'in adresi her sürümde değişiyor.

✅ **Projenin PHP sürümü bildirilir, tahmin edilmez** *(Sets, 11.08.2026: "statik olarak 8.5 girmeliyiz")*. `deploy.sh` "kurulu olanların en yenisi"ni arıyordu, `preflight.sh` çıplak `php`ye düşüyordu (sunucuda 8.3) — iki cevap, ve bugünkü deploy tam bu ayrışma yüzünden durdu: deploy doğru yorumlayıcıyı bulmuş, kendi ön koşul kontrolü 8.3'e bakıp "karşılanmıyor" demişti. Sürüm artık **`.php-version` = 8.5**; seçim `deployment/scripts/php-bin.sh` içinde (o sürüm yoksa deploy başlamadan durur), `composer.json` da aynısını bildiriyor (`require ^8.5` + `config.platform 8.5.0`, lock yenilendi). ⚠ "En yenisi" zaten yanlış soruydu: makineye php8.6 kurulması, kimse istemeden projenin yorumlayıcısını değiştirirdi.

⚠ **Planlama kuyruğu kabuğu iki kez çiziyordu** *(EGT-94, 11.08.2026)*. `office.js` her sayfaya `PanelLayout` veriyor (kendi layout'unu bildirmediyse); `Office/Planning/Queue.vue` bir kez de kendi şablonunda sarmalıyordu — iki ray, iki başlık, her menü bağlantısından iki tane. Paneldeki **tek** böyle sayfaydı, o yüzden başka hiçbir yer bozuk görünmüyordu ve incelemede yakalanmamıştı: ikinci kabuk kusursuz çiziliyor, yalnız birincinin içinde. Arch testi eklendi (`FrontendTest` → "never draws the panel shell twice").

⛔ **Firmanın ilk kullanıcısı panele hiç giremiyordu** *(EGT-93, Tayfun; kararlar Sets, 11.08.2026)*. Her tenant `owner` rolüyle donatılıyordu — bütün müşteri izinlerini taşıyan rol — ve **o role kimse atanmıyordu**. Panel yetkisi olmayan kişiyi 403 ile geri çeviriyor (doğrusu bu: koordinatörü katılımcıdan ayıran şey bu), yetki veren matris ise `customer.manage_organization` arkasında. Yani firmanın ilk kullanıcısı 403 alıyordu ve bunu düzeltebilecek kişi, giriş yapamayan kişiydi.

- ⛔ **Firma açılırken yetkili kullanıcı da alınır** *(Sets: "bu bir hata, müşteri oluşturulurken yetkili kullanıcının bilgileri de alınmalı")*. Ofisteki «Firma ekle» formu artık **ad soyad · e-posta · telefon · TCKN** istiyor; ⚠ TCKN burada **zorunlu değil** (3E hesabı açarken numara elinde olmayabilir), firmanın kendi personel ekranında **zorunlu**. Kişi açılır, **firma yöneticisi olarak atanır**, davet e-postası gider — hepsi tek işlemde.
- ⛔ **İlk kişi otomatik yönetici** *(Sets)*: `CreateCustomerPerson` firmada rolü tutan kimse yoksa `owner`'ı verir. Sonraki kişiler yetkisiz gelir; onları firma kendi matrisinden yetkilendirir. ⚠ Bu bir **bootstrap**, "yeni olana yetki" kuralı değil: yöneticisi olan firmaya 3E kendiliğinden yetki dağıtmaz.
- ⚠ Yetki genişlemesi yalnız boş firmada: ilk kişiyi adlandırmak **firmayı açmanın parçasıdır**, o yüzden `customer.create` de yeterlidir; sonraki herkes `customer.update` ister. Davet ise firmayı düzenleme hakkına bağlı — yalnız açma hakkı olan kişi firmayı çalışır hâlde bırakır, daveti kartı açan gönderir.
- ⛔ **Yetkisiz kişi artık çıplak 403 görmüyor** *(Sets)*: «Hesabınız hazır — firma paneli için henüz yetkiniz yok», yetkiyi kimin nereden vereceği yazılı, katılımcıysa kendi eğitim ekranına bağlantı. ⚠ Yalnız **hiçbir yetkisi olmayan** kişi için; belirli bir ekranın iznini tutmayan kişi yine 403 alır — o, firmanın içindeki sınırın çalışıyor olmasıdır.
- Müşteri kartındaki kişi listesi kimin yönetici olduğunu satırında söyler (`CustomerPeople.is_administrator`).
- ⛔ **Davet gönderme önce sorar, sonra ne olduğunu söyler** *(Sets, 25.08.2026, EGT-199 — Tayfun: "davet gönder butonuna basınca işlem yapılmıyor")*. Davet gidiyordu (canlıda yedi `customer_user.invited` kaydı, sonuncusu 24.08 10:38); **ekran hiçbir şey söylemiyordu** ve rozet «Davet bekliyor» olarak kalıyordu — rozet davetin gittiğini değil kişinin bağlantıya tıkladığını gösterir. Çalışan düğme bozuk okunuyordu. Yeni hâl: **onay penceresi** (kim, hangi adres) → gönderim → *"Davet talebiniz alındı. E-posta sistem tarafından otomatik gönderilecektir."* ⚠ Gerekçe iki katlı: bir mektup birinin posta kutusuna gidiyor, listedeki yanlış satıra denk gelen tek tıklama onu göndermemeli; ve sessiz başarı, başarısızlıktan ayırt edilemez.

⛔ **Görevlerim, firma görevinde 500 veriyordu** *(EGT-96, Tayfun; ölçüldü 11.08.2026)*. `NotificationTargets::load()` bir görevin/bildirimin **konusunu** çözerken ofis okuyucusu için her zaman `TenantContext::forOffice()` kullanıyordu — yani **kapsamı boş** bir bağlam. `customers` politikası `id = ANY(app.tenant_ids())` ve düşecek ikinci dalı yok: bir görev bir FİRMAYI işaret ettiği anda okuma *"tenant context not set"* ile patlıyor ve sayfa 500 dönüyordu. ⚠ Diğer konu türleri (eğitim, teklif, fatura, eğitmen) **tesadüfen** kurtuluyordu — onların politikalarında ofis dalı var ve boş liste okunmadan kısa devre oluyor.

Düzeltme, ürünün başka yerlerinde zaten kurulu olan desene hizalıyor (`PlanningQueue`, `TrainingRequestQueue`): ofis okuyucusu **kendi erişim kapsamında** okur; kapsamı boşsa yalnız ofise ait kayıtlar çözülür, müşteriye ait olanlar tanımı gereği erişilemez olduğu için adsız/bağlantısız kalır. Aynı okuma bildirim çubuğunu da beslediği için o da düzeldi.

⛔ **Davet akışı tasarımda var, ekranda yoktu** *(ölçüm 10.08.2026)*. İşleyiş şu olmalıydı ve Sets'in kurgusu buydu: **personel eklenir → davet edilir → giriş yapar → "yetki bekleyen" olarak görünür → yetkileri verilir.** Bugünkü hâl: yetki matrisi şirketteki **herkesi** aynı biçimde listeliyor, kimin giriş yaptığına dair işaret yok (`MyOrganisation.php:180-208`), ve ⛔ tasarım `11`'in matris başlığına koyduğu **"+ Kullanıcı davet et"** bağlantısı (`design/11 …dc.html:195`) hiç uygulanmamış — davet yalnız Personel satır menüsünde. Tayfun'un *"epey aradım, bulana kadar uğraştırdı"* dediği yer burası; yeni bir istek değil, **teslim edilen tasarımın atlanmış parçası** (kapsama matrisinin yakalaması gereken cins). ⚠ Sinyal elimizde ve kullanılmıyor: `magic_links.used_at` "davet gitti / kullanıldı"yı, `password` + `email_verified_at` "hesabını kurdu"yu söylüyor.

⚠ **`DataTable` dilim satırı sunucu sorgusunda sessizdi** — rozete tıklanıyor, vurgu değişiyor, liste aynı kalıyordu (`server-query` altında `filtered` satırları olduğu gibi geri veriyor). Artık `update:query` `slice` de taşıyor; yerel süzen tablolar etkilenmedi.

**Kenar hâller — tasarım `38` (02.08.2026, matris: `docs/reviews/tasarim-38-kapsama.md`).**

**Teklif isteği üç adım oldu** *(B5)*. 1 · **Kapsam**: nereden başlanacağı sorulur — *sistemin tespit ettikleri* (sertifikası eşiğe giren + işe giriş kuralındaki personel; `RequestStartingPoints`), *kendim seçeyim*, *önceki istekten kopyala*. Seçim yalnız 2. adımı **doldurur**, hiçbir şey göndermez; isimler tasarım `14`'teki gibi öneridir. 3 · **Özet**: gruplar, KVKK cümlesi, "gönderince ne olacak" ve "gönderdikten sonra düzenlenemez".

**`38` matrisinin kapanan satırları** *(Sets, 02.08.2026)*: teklif isteği **oluşturulduğu anda referans kodu rezerve edilir** — resmî bir taleptir, olumlu ya da olumsuz sonuçlanması değiştirmez (B5-a) · alıcı hem rehberden seçilebilir hem elle yazılabilir, ⚠ mümkün olduğunca **rehberdeki kayda yönlendirilir** (B6-a) · gelen e-postadan termini metinden okuma **kapsam dışı** — ⛔ yanlış yorumlayabileceğimiz çıkarımlar, kullanıcının hatasına gerekçe olarak bizi gösterdiği yerdir; gerekiyorsa görevi kendisi oluşturur (B7-a) · uyum kartındaki eşik rozeti eklenebilir, **görerek karar verilecek** (C4-a).

**Alıcı rehberden de seçilir** *(02.08.2026)*: teklif ekranındaki gönderme penceresi firmanın kayıtlı kişilerini alanın altında çip olarak sunar; elle yazma açık kalır ama ⚠ **kayda yönlendirilir** — elle yazılan adres bir daha bulunamayacak bir kişidir.

**Teklif isteğinin kodu gönderim anında söylenir** *(02.08.2026)*: kod zaten oluşturma anında rezerve ediliyordu (AD-19); eksik olan müşterinin onu **görmesiydi** — gönderim sonrası bildirim artık kodu yazıyor.

**Bir yazışma kaydı, birden çok alıcı** *(B6)*. `correspondence_recipients`: kayıt tektir, alıcılar listedir. ⛔ İki kez göndermek akışta iki satır üretir ve yanıt hikâyenin yarısını taşıyan konuşmaya düşer. Teslim ve **açılma izi alıcı bazındadır** (`RecordLetterOpened`, Postmark `Open` webhook'u — alan adları resmî dokümandan doğrulandı). ⛔ Bu kişi bazlı kullanım izleme değildir (AD-21): 3E'nin **kendi giden mektubuna** dair teslim kanıtıdır, bounce ile aynı sınıf.

**375px'te iki kaydırma yüzeyi yok** *(C2)*. `ScrollToAccept` telefonda kendi yüksekliğini bırakır; belge **sayfanın kendisi** olarak akar, ilerleme üstte sabit şeritte, karar paneli altta sabit. "Okundu" ölçümü sayfa kaydırmasındandır. ⛔ Bu iddia **ölçüldü**, varsayılmadı: `tests/Browser/scroll-to-accept.spec.js` (375 ve 1024) — fixture build'i `vite.fixtures.config.js`, koşu `npm run test:fixtures`.

**Uyum eşiği artık yazılabiliyor** *(C4)*. `customers.compliance_warning_days` (30/60/90/120, boş = 3E varsayılanı) portal organizasyon ekranından ayarlanır; sertifika ekranı **hangi eşikle** liste çıkardığını yazar ve "bu N kişiden grup oluştur" bağlantısıyla sihirbazın 1. adımına gider. ⛔ Eşik sabit sayı değildir — "dolacak" göreli bir ifadedir ve eşiği görünmeyen listeye kimse güvenmez.

**Filtre → arama → sayfalama zinciri** *(C5)*. Sekme sayaçları **aramadan bağımsız** tür toplamlarıdır; başlıktaki sayı filtre+arama sonrasıdır. Filtre değişince arama korunur, sayfa 1'e döner. Sonuç yoksa "Tümü'nde N sonuç var" tek tıkla genişletir.

**Karar (Sets, 29.07.2026):** ofis tarafı müşteri yönetimi **spec turu olmadan doğrudan plana** yazıldı — kuralları çatı §60–67, `omurga-spec.md` §3 ve AD-23'te belli. Plan: `docs/plans/musteri-yonetimi-plan.md` v1.0 (8 görev, 16 kabul kriteri, **EGT-62**). Müşterinin kendi organizasyonu ve lokasyonları sonraki tura kalır; onları teklif isteği ve katılımcı listesi kullanır, planlama kullanmaz.

| Plandaki karar | § |
|---|---|
| Yeni tablo yok — kimlik chunk 5'te, atama chunk 6'da doğdu | K-1 |
| Müşteri **açma** tenant bağlamı istemez, **güncelleme** ister (RLS: INSERT açık, UPDATE `write_tenant_id`) | K-2 |
| Ekran deseni `Office/Users`'tan alınır — ofis müşteri yönetimi tasarımda yok | K-3 |
| Davet parola belirletmez; `purpose: 'invitation'` taşıyan magic link'tir | K-4 |
| Satışçı ataması iki yerden yapılır, tek Action | K-5 |
| Pasifleştirme gerekçe ister | K-6 |

---

⛔ **Geçerlilik tarihi sorulmaz, süre sorulur** *(EGT-107, Tayfun: ileri tarihe gidemedi; kararlar Sets, 12.08.2026)*. Sertifika girişinde geçerlilik ikinci bir takvimdi ve takvimde yalnız ay okları vardı: 01.10.2025 alışlı iki yıllık bir sertifikanın bitişine ulaşmak **on dört tıklamaydı**, doğum tarihine ulaşmak yüzlerce. Sets'in sorusu daha derini gösterdi — *"bu tarz formlarda formu kolaylaştırabilir miyiz?"*: kimse bitiş tarihini **bilmez**, sertifikanın iki yıllık olduğunu bilir.

- **Süre kutucukları** (`ValidityField`): 1/2/3/5 yıl · Süresiz · Kendim gireceğim. Seçilen süre veriliş tarihine eklenir, sonuç **ekranda yazıyla** görünür — sessizce yazılan tarih, değiştirdiği takvimden kötüdür.
- **Önceden işaretli olan tanımdan gelir** — eğitim tanımının `renewal_months`'ı, belge türünün `default_validity_months`'ı; kutucuğun yanında `(tanımlı)` yazar. **Zorlamaz** (kırmızı çizgi 14): değer düz bir tarihtir, "Kendim gireceğim" her zaman durur.
- **Teklif geçerliliği aynı kontrole bağlandı** *(EGT-125, Sets 12.08.2026)*: orada süre **gün** cinsinden ve **hiçbiri önceden işaretli gelmez** — teklifin ne kadar açık kalacağı ticari bir karar ve arkasında admin-tanımlı alan yok, varsayılan seçmek kuralı ekrana gömmek olurdu.
- **Kapsam:** veriliş+geçerlilik çiftinin olduğu **beş** yer — ofis yoklama sertifikası, ofis kurum belgeleri, portal firma evrakı, portal personel sertifikası beyanı, portal yeni personel sertifika satırları. Eğitmen sertifikasının veriliş tarihi yok, dolayısıyla dayanağı da yok: orada çıplak takvim kalır.
- **Takvimin kendisi de düzeltildi:** alan artık `<input>` (eskiden düğmeydi, klavyeyle doldurulamıyordu), çift ok bir yıl atlar, ay adı yıl ızgarası açar.
- ⚠ Firma profilinde bu öneri zaten vardı ama **sessizdi ve hatalıydı** — `setMonth` ayın 31'ini bir sonraki aya taşırdı (31 Ocak + 1 ay = 3 Mart). `addMonths` ayın son gününe sabitler.

### 4.8 Katılımcı

**Ne yapar:** magic link ile girer, oturum beyanı yapar, anketi doldurur, eğitim geçmişini görür.

**Durum:** kimlik ✓ (müşteri kullanıcısının yeteneği) · ekran ✓ (`Portal/Participant/Panel`, `/katilimci`; magic link → kimlik teyidi → panel)

| Karar | Kaynak |
|---|---|
| Katılımcının dashboard'u yok — tek akış | `HANDOFF.md` §Rol→dashboard |
| Yoklama ikilidir: imzalı liste **esastır**, katılımcı beyanı zaman damgalı ikincil kanıttır | `HANDOFF.md` §9, Y-2016 md.9/1-c |
| Sınav tekrarı yoktur — başarısız katılımcı eğitimi baştan alır | `mevzuat-kurallari.md` §12, `HANDOFF.md` §9 |
| Sertifika bölümü **"eğitim geçmişi / uyum durumu"** olarak kurgulanır; Bakanlık kodu gösterilir + zorunlu uyarı metni | AD-28, `HANDOFF.md` §14 |
| Video kaydı yalnız İdare ile paylaşılır, hiçbir panelde gösterilmez | AD-13, `HANDOFF.md` §9 |

**Ekranlar:** `50 Katilimci Paneli` — tasarım hazır, kod yok

**Ticket:** EGT-27 (spec, açılmadı)

---

## 5. Çapraz konular

### ⛔ Üç ortam, üç ayrı iş — ve backoffice'e MÜŞTERİ VERİSİ konmaz *(Sets, 07.08.2026)*

| | Backoffice | Ofis | Panel (müşteri) |
|---|---|---|---|
| Host | `backoffice.` | `ofis.` | `panel.` |
| Kimlik tablosu | `backoffice_users` | `users` | `customer_users` |
| **`tenant_id` kolonu** | **yok** | **yok** | **var** |
| İşi | sistem yönetimi: tanımlar, mevzuat, şablonlar, izin şablonları, fiyat listeleri | operasyonun tamamı; **yetkisi olduğu alanda** müşteri verisi | yalnız kendi tenant'ı, RLS ile tam izolasyon |

⛔ **Ofis kullanıcısının tenant'ı YOKTUR — olamaz da.** O 3E'nin kendi personelidir, bir müşteriye ait değildir. Sahip olduğu şey tenant değil **kapsamdır**: hangi müşterilerin verisine erişebileceği. Bugün iki yoldan hesaplanır — `customer.view_all` izni (hepsi) ya da `office_user_customers` ataması (yalnız verilenler). Karar **admin'indir**, kodda gömülü değildir (AD-08).

⛔ **Backoffice'e müşteri verisi BİLİNÇLİ olarak konmadı** *(Sets, 07.08.2026)*: *"backoffice'e bazı verileri özellikle eklemiyorum ki 'şunu da backoffice'e koyalım, bunu da koyalım' demesinler, ofisi kullansınlar diye. Backoffice'i mümkün olduğunca sistem yönetimi ile sıkıştırmak istiyorum."*

⚠ **Bu bir yetki tavanı ya da mimari kısıt DEĞİL — bugünkü stratejidir** *(Sets, 07.08.2026: "backoffice kuralı kesin bir kural değil. Strateji olarak böyle ilerliyorum şu anda. Yarın bu konuda fikir değiştiririm ya da gerçekten backoffice'ten bir şeyin görülebilmesi gerektiğini düşünürüm, o zaman eklerim.")*. Müşteri verisinin bugünkü tam yeri — detay ekranlarıyla birlikte — **ofistir**, ve orada yalnız yetkisi olan görür. Backoffice'e müşteri verisi eklemek teknik olarak mümkündür ve **yasak değildir**; bugün yapılmıyor, sebebi de kapsamın kaymasını önlemek.

⚠ Dolayısıyla bu satır bir **red gerekçesi değildir**: *"backoffice'e şunu ekleyelim"* önerisi geldiğinde cevap *"kural yasaklıyor"* değil, **"bugünkü strateji bu, gerçekten gerekiyorsa Sets'e sorulur"**dur.

⚠ **İleride destek modülü gelirse** (destek personeli müşteri kaydına bakacaksa) bu, backoffice'te **kısıtlı bir yetkiyle** yapılabilir — ama muhtemelen **kendi subdomain'ini** alır. Sets bunu örnek olarak verdi; karar o gün alınır.

### Host topolojisi — dört subdomain *(Sets, 03.08.2026)*

⛔ **Müşteri paneli kendi subdomain'inde çalışır: `panel.<domain>`.** Gerekçe Sets'in kendi ifadesiyle: *"subdomain bazında ayırmamız tüm politikaları bunlara göre kurgulamamızı sağlıyor, hem ek güvenlik katmanı hem izolasyon tarafında bizi daha rahat yönetilebilir bağımsız alanlara kavuşturuyor."* Yani ayrım kozmetik değil; çerez alanı, oturum, CSP/CORS, passkey bağlama ve WAF/rate-limit politikaları host başına kurulur.

| Host | Ne çalışır | Kimlik alanı |
|---|---|---|
| `<domain>` (düz) | **Landing page** + müşteri paneline giriş bağlantısı | — (anonim) |
| `panel.<domain>` | **Müşteri paneli** — kendi domaininin **KÖKÜNDE** + katılımcı ekranları | `customers` + `customer_users` |

⛔ **`/portal` diye bir şey YOK** *(Sets, 03.08.2026)*: *"panel'de /portal diye bir şey görmek istemiyorum. Panel direkt olarak kendi domaininde yaşayacak, ek klasör koymaya gerek yok."* Yani `/portal/personel` değil `panel.<domain>/personel`; `/portal` değil `panel.<domain>/`. Düz domainde landing page durur ve üzerindeki **"Müşteri girişi"** bağlantısı panele yönlendirir.
| `ofis.<domain>` | Ofis paneli (planlamacı · satışçı · eğitmen · operasyon) | `users` |
| `backoffice.<domain>` | Filament backbone admin | `backoffice_users` |

✅ **Uygulandı (03.08.2026, Kuşak 0 · 0.3).** `config/app.php` dört host tanımlıyor; `routes/panel.php` doğdu ve `bootstrap/app.php` onu `app.hosts.panel`'e bağlıyor. `/portal` öneki 22 dosyadan silindi; `HostRoutingTest` artık **hiçbir host'ta `portal` ile başlayan route kalmadığını** ölçüyor. Düz domainde `resources/views/landing.blade.php` duruyor — **"Müşteri girişi"** düğmesi `https://panel.<domain>/giris`'e gider. Davet bağlantıları (`InviteCustomerPerson`, `InvitePortalPerson`) ve `IdentityDomain::host()` de panele döndü.

⚠ **Landing page bilinçli bir yer tutucudur:** tasarımcı landing çizmedi, uydurulmadı. Taşıdığı tek şey topolojinin ondan istediği kapı. Tasarım geldiğinde yerine geçer.

✅ **Rol × ekran erişim matrisi çıkarıldı (03.08.2026).** `tests/Browser/role-access.spec.js` — yedi izin paketinin her biriyle **gerçekten giriş yapılıp** 27 adreslenebilir ofis ekranı ziyaret ediliyor. Tam matris: `docs/reviews/rol-ekran-matrisi.md`.

| Paket | Açık | 403 |
|---|---|---|
| Operasyon direktörü | **27** | 0 |
| Satış yöneticisi | 21 | 6 |
| Muhasebe | 19 | 8 |
| Satışçı | 18 | 9 |
| Planlamacı | 16 | 11 |
| Operasyon — başlangıç | 15 | 12 |
| Eğitmen | 7 | 20 |

⛔ **Tek iddia: direktör her ekranı görür.** Giremediği bir sayfa ya verilmemiş bir izindir ya da kimsenin ulaşamadığı bir ekran — ikisi de hata. ⚠ Diğer paketler için matris **rapordur**: planlamacının neyi göreceği chunk6'da kayıtlı bir karar, burada assert etmek yetki modelini ölçmek yerine uydurmak olurdu.

⛔ **Matrisin bulduğu üç 500 — direktörle gezen turun göremeyeceği cinsten:** `/yonetim` (operasyon personeli + eğitmen), `/satis/teklifler/yeni` ve `/satis/planlama-talepleri/yeni` (satışçı). Üçünün de sebebi aynı: **boş erişim**. `TenantContext::run()` boş kapsamı reddediyor — ki doğru, çoğu çağıran için "hiçbir şey" kimliğin hiçbir şeye çözülmesi demek ve kapsamsız sorgu bu katmanın önlemek için var olduğu sızıntı. Ama bazı kişiler gerçekten kimseye erişmez: ataması olmayan operasyon personeli, eğitmen, ilk gününde bir satışçı. Onların ekranı **boş liste** çizmeli. `TenantContext::reachIsEmpty()` eklendi; adı sorusunu söylüyor — *"bakılacak bir şey var mı"*, *"bakabilir miyim"* değil.

⚠ **Turun kendisi 25 dakikadan 3.4 dakikaya indi** ve sebebi bir ürün gerçeğiydi: 768px altında tablo hâlâ belgede duruyor (`hidden md:block`), tur **görünmeyen** satıra tıklayıp iki kez actionability zaman aşımını bekliyordu. Artık satırlar görünür olanlardan seçiliyor, her adımın süre bütçesi var ve aşan adım bulgu olarak kaydedilip tur devam ediyor.

✅ **Tarayıcı turu kuruldu ve koştu (03.08.2026).** `tests/Browser/{panel,office,backoffice}-health.spec.js` — üç host, 104 ekran, 375 ve 1440 px, gerçek ekranlardan gerçek TOTP ile giriş. Ekran listesi `route:list`'ten **üretiliyor**, elle tutulmuyor: yeni bir ekran turu atlayamaz. Tam katalog: `docs/reviews/tarayici-turu-bulgulari.md`, envanter `docs/reviews/ekran-envanteri.md`.

⛔ **Turun ilk çıktısı turun kendisi hakkındaydı ve kural olarak yazılmalı:** ilk koşuda müşteri girişi sessizce başarısız oldu, sonraki her `goto` giriş ekranına döndü ve tur *"20 ekran gezildi, 18'i sağlıklı"* dedi — giriş ekranı hakkında, on sekiz kez. **Yanlış sayfayı ölçüp "her şey yolunda" diyen bir tur, turu olmamasından kötüdür.** Artık her kimlik adımı kontrol ediliyor ve giriş tamamlanmadıysa tur patlıyor.

**Turun bulduğu ve düzeltilenler:**

| # | Bulgu | Ölçü |
|---|---|---|
| 1 | **Var olmayan renk sınıfları** — `ink-500` (254 kullanım, 66 dosya), `ink-700`, `ink-900`, `warning-700`, `text-link` | Tailwind tanımadığı sınıfı **sessizce atar**; o metinler aylardır ebeveyninin rengini miras alıyordu. Arch testi yazıldı |
| 2 | **Müşteri paneli ikinci istekte 500 veriyordu** | Guard her istekte hesabı okuyor, `customer_users` satır bazlı güvenli, kapsam yok. `actingAs` bu yolu atladığı için hiçbir test görmedi → `CustomerUserProvider` |
| 3 | `/yonetim` her açılışta 500 | `OperationsAgenda` hiç tenant context açmıyordu |
| 4 | Teklif detayı 500 | Controller'ın içinde `Customer::query()` (AD-25 ihlali) → `CustomerPeople::forCustomerId()` |
| 5 | `PasswordInput` id'yi geçirmiyordu | Kök `div` olduğu için her `<label for>` bir div'i gösteriyordu — 7 ekran, 21 alan |
| 6 | Eğitmen listesi çöküyordu | `DataTable`'a üç zorunlu prop geçilmemiş, biri fonksiyon: `e.showingLabel is not a function`. `:empty-title` diye bir prop yok |
| 7 | **Eğitim detayına hiçbir yerden gidilemiyordu** | Tasarım `15`'in `›` kolonu hiç yazılmamış; `TrainingShow` ölü ekrandı |
| 8 | Teklif "Görüntüle" düğmeydi | Tasarım `13` anchor çiziyor; düğme yeni sekmede açılmaz, klavyeye cevap vermez |
| 9 | `/teklif-istegi` JS hatasıyla çöküyordu | Controller `locations` objesini array bekleyen prop'a veriyordu |
| 10 | 375'te üç panel ekranı yatay taşıyordu | Ham tablo (kart dönüşümü yok) + sarılmayan toolbar |
| 11 | **Demo yöneticilerin şifresi yoktu**, yedek kodlar düz metindi | Portal kişisi davetle yaratılıyor, şifre koymuyor; `Hash::check` hash olmayan değerde **istisna fırlatıyor** → ikinci faktör 500 |

⏸ **Karar bekleyen iki başlık:**
- **İçerik paketi kataloğu backoffice'te TANIMLANAMIYOR.** `ContentPackagePolicy` yalnız ofis kimliğini tanıyor ve `create()` düz `false`; backoffice yetki ailesinde `content_package.*` izni de yok. Politikanın kendi dokümantasyonu *"paket backoffice'te tanımlanır"* diyor. Üç ekran kalıcı 403; paket bugün yalnız seeder ile var olabiliyor. **Hangi izin yönetsin?**
- **32 sayfa ham `<table>` yazıyor, yalnız 6'sı `DataTable` kullanıyor.** Tablo→kart dönüşümü (`02`, HANDOFF §81) organizmada zaten var. Panelin üçü elle düzeltildi. **Ham tablolar `DataTable`'a taşınsın mı?**

⚠ **`panel.` host'u nginx'te tanımlı ve çalışıyor; eksik olan tek şey Cloudflare'da bir A kaydı** (`panel.egitim.dev.sets.fyi` → 10.31.1.25). Wildcard sertifika zaten kapsıyor. Tur bugün Chromium'un `--host-resolver-rules` bayrağıyla köşeyi dönüyor; kayıt açılınca o satır silinecek (`playwright.config.js`).

✅ **Müşteri giriş kapısı açıldı (03.08.2026, Kuşak 0 · 0.4).** `GET /giris` yoktu ve misafir 405 alıyordu — panelin kapısı hiç açılmıyordu. Yazılanlar: `Portal/Auth/SignIn` (tasarım `10`'un müşteri yarısı, sağ tanıtım kolonuyla), magic link "gönderildi" ve "süresi doldu" halleri, kimlik teyidi ekranı, **müşteri ikinci faktörü**, şifre sıfırlama 1/2 ve 2/2. Kapsama matrisi: `docs/reviews/tasarim-10-musteri-kapsama.md`.

⛔ **Aynı turda kapanan iki sessiz açık:**
- **Müşteri ikinci faktörü girişte hiç sorulmuyordu.** `CustomerUser::hasCompletedMfa()` yalnız `two_factor_confirmed_at`'e bakıyor; yani doğrulayıcı uygulamayı **kurmak** `customer.approve_quote`'un MFA kapısını geçmeye yetiyordu. Kişi faktörü kuruyor, giriş onu hiç kullanmıyor, hesap tek faktörlü kalıyordu. Ofisteki adımın aynısı yazıldı (`PendingSecondFactor` artık kimlik alanı başına anahtarlanıyor).
- `two_factor_recovery_codes` **cast'siz**di: yazarken JSON'a dönüyor, okurken ham string geliyordu. Sütunu hiçbir kod okumadığı için hiç patlamamıştı.

✅ **Dört başlık karara bağlandı** *(Sets, 03.08.2026 — matris §6)*:

| Konu | Karar |
|---|---|
| Mockup'taki *"E-posta veya cep telefonu"* | **Şimdilik e-posta.** Telefonla giriş **ertelendi, reddedilmedi** — *"SMS entegrasyonu yaptıktan sonra bakacağız… provider ekleyeceğiz sonuçta çok dert olmaz."* Açıldığı gün gereken: geçit fonksiyonuna telefon hash'i + `phone_hash` kolonu + etiketin tasarımdaki metne dönmesi |
| Birincil düğme rengi (mockup accent, katalog ink) | **Katalog kararı ile devam** — *"doğrusunu yapmışsın."* Mockup'ın accent düğmesi uygulanmayacak bilinçli sapmadır; gerekçesi `00 Tokenlar` (accent bağlantıyı ve odağı taşır) |
| Magic link süresi (mockup 15 dk, ayar 30 dk) | **30 dakika** — *"mockup burada örnek, bizim planımız ne ise ona uyacağız."* Ekran zaten ayarı okuyor; kod değişmedi |
| 375px'te 48px dokunma hedefi | **Düzeltildi, tasarıma uyuldu** — *"o tasarımcının sorumluluğu."* Ölçüm kuralın **yarım** uygulandığını gösterdi (aşağıda) |

⛔ **Dokunma hedefi kuralı yarım uygulanmıştı ve bulgu buydu.** `HANDOFF §11` zaten *"dokunma hedefi ≥44px (mobil form 48px)"* diyor; **bütün girdiler** ilk günden `min-h-12 md:min-h-11` taşıyordu, `BaseButton` ise düz `min-h-11`di. Yani formun **alanları** başparmak için büyüyor, **gönder düğmesi** büyümüyordu — başparmağın gerçekten isabet etmesi gereken tek kontrol. `BaseButton`, `PasswordInput`'un göster düğmesi, `DateRangePicker`, `FileDrop`, anket ölçeği ve giriş ekranının metin düğmesi düzeltildi. Bir daha kaymasın diye arch testi yazıldı (`FrontendTest` → *"grows every form control to 48px on a phone"*); breakpoint'siz `min-h-11` taşıyan form kontrolü testi kırar. ⚠ Kapsam form kontrolleridir — gezinme rayı, tablo sayfalayıcı ve kenar çubuğu satırı *"mobil form"* değil, tabanları 44px ve zaten sağlıyorlar.

⚠ **Ayrıca açık kalan üç bulgu** (matris §8): yedek kod saklamada ofis/müşteri ayrışması · katılımcı olmayan birinin magic link takibi 404 ile bitiyor · **müşteri girişinde hız sınırı yok** (`ThrottlesSignIn` ofiste var, müşteride yok).

### Kimlik ve yetki
Üç bağımsız kimlik alanı: ofis (`users`), backoffice (`backoffice_users`), müşteri (`customers` + `customer_users`). **Hiçbir yetki tablosunu paylaşmazlar** ve sistem aralarında eşleştirme yapmaz — aynı kişinin iki kaydı olabilir, sistem bunu bilmez. → `omurga-spec.md` §3, I-5

### Backoffice paneli — iç araç *(Sets, 22.08.2026)*

⚠ **Backoffice'in kendi «parolamı unuttum» mektubu İngilizce çerçevede gidiyor ve bu bilinçli.** Filament'in hazır bildirimi kullanılıyor; çeviri yapılmadı çünkü *"backoffice tarafı kalabilir, internal o"*. **Davet** mektubu bunun istisnası: o bir dış adrese, hesabı henüz olmayan kişiye gider ve Türkçe şablonda çıkar (`BackofficePasswordResetLink`, EGT-195). Ölçüt kimin okuduğu: mektubu okuyan kişi 3E personeliyse iç araçtır, müşteri/eğitmen/katılımcıysa Türkçe zorunludur.

### Saat ve tarih — panelin iki biçim kuralı *(Sets, 22.08.2026, EGT-196)*

⛔ **Saat tutan her alan iki ondalık basamak tutar** — çeyrek saat (1,25) sıradan bir değerdir ve tek ondalıkla yazılamaz. Kapsam: eğitimin ilk/yenileme/sınav süresi, oturum süresi, müfredat konu saati, eğitmen hakediş saati, fiyat bandı saati. ⚠ **`max_daily_hours` hariç** — mevzuattan gelen tam sayı bir tavan (TY-2019 md.17/1: bir eğitim günü sekiz saat), çeyrekli tavan metnin söyleyebileceği bir şey değil.

⛔ **Tarih kutuları tarayıcının kendi takvimi değildir.** Panel genelinde `native(false)` + `d.m.Y` varsayılan olarak kurulu (`AppServiceProvider`); tek tek alanda tekrarlanmaz. Sebep: tarayıcının takvimi biçimini kendi diline göre seçiyor ve İngilizce tarayıcıda 26.07.2019 tarihi 07/26/2019 diye çiziliyordu — kaybolan bir şey yok, ayırt edilemeyen bir şey var.

### Tenant izolasyonu
İki savunma hattı: uygulama kapsamı + PostgreSQL RLS. Tenant bağlamı **yalnız transaction içi `SET LOCAL`** ile kurulur (pgbouncer tuzağı). Her tablo ya tenant-kapsamlıdır ya `config/tenancy.php` muafiyet listesindedir; test tam sayıyı doğrular. → AD-02, AD-03, AD-04

### Evrak
Tek şablon tablosu (`document_types`), sahip başına ayrı belge tablosu. `uploadable_by`: `operations` / `owner` / `both`. Kritik belge **veritabanı seviyesinde** silinemez (trigger). → AD-24 Ek 2, AD-11

### Audit
Üç katman: yazma olayları, **okuma (erişim) logu**, kullanım logu. Audit yazımı işlemle **aynı transaction'da** olur, kuyruğa atılmaz. Zincir hash'i dış tanık deposuna yayımlanır (`Sets/3eegitim-anchor`). ⚠ Delil sınıfı **takdiri delildir**, hukuk teyidi bekliyor → EGT-51[3]. → AD-06, AD-21

### Bildirim
Postmark (mail) + İletiMerkezi (SMS) + uygulama içi + Discord (iç olaylar). Ticari iletide İYS kapısı. Kademeli süre bildirimleri. **Altyapı henüz yazılmadı** → chunk 7, EGT-37. → AD-17, AD-18, `omurga-spec.md` §10

### Referans kodları
`PREFIX-YYYY-XXXX`, 2 harf ön ek, rastgele 4 karakter son ek. Ardışık numara **yasak** (iş hacmi sızdırır). Benzersizlik `(yıl, son ek)` uzayında — PL→EG dönüşümü son eki koruduğu için. → AD-19

### Yazışma
Kayda bağlanır; kayıt türü **eğitim** veya **teklif**. Her tür kendi inbound adresini taşır. Gelen mail doğrulanmamış içeriktir. → AD-20

### Ortak bileşenler — kullanılması zorunlu

| Ne | Bileşen | Not |
|---|---|---|
| Telefon | `molecules/PhoneInput.vue` | E.164 saklar, ülke seçer, maske uygular, harfi reddeder. **Veriyi kendi alır** — çağıran yalnız `v-model`, `id` ve sabit hat için `kind="landline"` yazar (alan kodu select olur, 83 kod) |
| E-posta | `atoms/EmailInput.vue` | `type=email` + inputmode + autocapitalize kapalı; çıkışta trim + küçük harf |
| Aranabilir seçim | `atoms/SearchableSelect.vue` | Katlama `lib/search.js` üzerinden, veritabanıyla birebir |
| Tek tarih | `molecules/DatePicker.vue` | Açılır takvim; native date input **kullanılmaz**. **Üç yol:** alana yazmak (`01.10.2027`), çift okla yıl atlamak, ay adına basıp yıl ızgarası açmak (EGT-107) |
| Tarih aralığı | `molecules/DateRangePicker.vue` | Açık takvim, gün sayısı altta |
| Geçerlilik bitişi | `molecules/ValidityField.vue` | Veriliş tarihi olan her yerde **zorunlu**: takvim değil süre kutucukları, hesaplanan tarih altta yazıyla. `unit="months"` → 1/2/3/5 yıl · Süresiz (sertifika, belge); `unit="days"` → 1/2 hafta · 30 gün (teklif geçerliliği, EGT-125 — *Türkiye'de teklif 1-2 hafta, en fazla 1 ay*). Arch testi çıplak takvime dönüşü kırar |
| Kutucukla tek seçim | `atoms/ChoiceChips.vue` | Üçten fazla kısa seçenek; `SegmentedControl` üçte biter, `ChipSelect` seçenekleri gizler |

Takvim aritmetiği tek yerde: `lib/dates.js` (ay hücreleri, baştaki boşluklar, gün adları). Metinleri `common` grubundan kendileri okuyor — sayfa `:labels` taşımıyor.

**Tasarımın form elemanlarının tamamı bileşen** *(30.07.2026)*:

| Tasarım | Bileşen |
|---|---|
| Searchable select (ok, renkli seçili satır, ikincil metin, async, sonuç yok + eylem) | `atoms/SearchableSelect.vue` |
| Multi-select — chip'li, "tümünü seç" | `molecules/MultiSelect.vue` |
| Cascade — il → ilçe | `molecules/CascadeSelect.vue` |
| Date picker · Date-range · Time · Datetime | `molecules/DatePicker · DateRangePicker · TimeInput · DateTimeInput` |
| Radio card | `molecules/RadioCardGroup.vue` |
| Drag-drop dosya + reddedilen dosya | `molecules/FileDrop.vue` |
| Para (₺, binlik ayraç, sağa hizalı) | `atoms/MoneyInput.vue` |
| Sayaçlı kontenjan (− 25 +) | `atoms/NumberStepper.vue` |
| Maskeli giriş (TCKN) · maskeli gösterim + "Göster" | `atoms/MaskedInput.vue` · `molecules/MaskedValue.vue` |
| Karakter sayaçlı textarea | `atoms/TextareaInput.vue` |
| Telefon · e-posta · şifre · OTP · toggle | `PhoneInput · EmailInput · PasswordInput · OtpInput · ToggleSwitch` |

⚠ Hepsi metnini `common` grubundan kendisi okuyor; sayfa etiket taşımıyor.

⛔ **Anket bileşeni yok** — tasarımda "Eğitmen değerlendirme (anket)" var ama bugün onu kullanacak ekran yok; katılımcı modülü turunda yapılır.

⛔ Ham `TextInput` ile telefon/e-posta alanı açmak yasak, **ve bileşene veri taşımak da yasak** (kırmızı çizgi 17). Ülke listesi, alan kodları ve varsayılan ülke ortak prop'ta (`contact`); sayfa hiçbirini geçmez. İki `FrontendTest` kuralı kırıyor — not değil, mekanizma.

⚠ Sabit hat alan kodları `turkiye-api` havuzundan geliyor (`scripts/area_codes.py`, 83 kod / 81 il). **Plakadan türetilemez:** İstanbul iki kod taşır (212/216), Ankara 312 ama plakası 06.

### İl ve ilçe verisi

81 il + 973 ilçe, `option_list_values` içinde tek listede iki katman (`IL`); ilçe kendi ilinin çocuğu. Kaynak paylaşılan `turkiye-api` havuzunun **`data/normalized/`** klasörü: yapı ve kimlikler TKGM'den (tapu-kadastro), **adlar PTT'den** — TKGM 973 ilçe adının 211'inde `ı`→`i` katlıyor (`Kadiköy`) ve katlanmış ad aranamıyor. Konsolidasyon: `php scripts/consolidate-turkiye.php [havuz-yolu]` → `database/data/turkiye-idari-birimler.json` (vendored, commit'li; deploy'da ne o repo ne ağ gerekir). Kod şeması: il `TR-34`, ilçe `TR-34-1103` (plaka + PTT ilçe kodu — slug değil, ilçe adı değişince kod değişmesin).

⛔ **Havuz çalışma zamanında okunmaz.** Konsolidasyon elle koşulur, vendored dosya commit'lenir; production, MBP build server ve her geliştirici makinesi yalnız o kopyayı görür. Testi var (`AdministrativeDivisionsTest`).

⛔ **İl her yerde ilişkiseldir, isim tutulmaz.** `trainings`, `training_packages` ve `suppliers` üçü de `city_option_value_id` FK'si taşır; hiçbiri il adını metin olarak yazmaz. Silme davranışı `restrictOnDelete` — bir ile bağlı kayıt varken il silinemez; artık var olmayan il **pasifleştirilir** (30.07.2026). Adres serbest metindir ve bilerek öyledir (spec §1.1) — referans verilecek bir adres listesi yok.

⚠ Mahalle ve posta kodu havuzda hazır (72.641 mahalle, hepsinde posta kodu) ama bu projeye **alınmadı** — bugün kimse istemiyor; müşteri lokasyonu turunda konsolidasyon script'i genişletilir.

⚠ İl listesi isteyen **kökleri** sormak zorunda (`OptionList::roots()`); tüm değerleri sormak 1.054 kayıt döner.

**İlçe kullanan yer:** tedarikçi kaydı (`suppliers.district_option_value_id`) — "İzmir'de otel" tek başına bir şeye yakın değil, il otuz ilçe geniş; eğitim yerine yakınlık kararı ilçeyle veriliyor *(Sets, 30.07.2026)*. Form ilçeyi seçilen ile göre süzüyor, il değişince ilçe temizleniyor. ⚠ Fiyat tarihçesi hâlâ **il bazında** karşılaştırıyor — ilçe yakınlık sorusu, fiyat sorusu değil.

⛔ **Bekleyen kapanış adresi:** eğitim kaydı hâlâ yalnız il + serbest metin adres tutuyor (planlama-spec §1.1). Eğitime ilçe eklenecek mi, karar Sets'te.

**Tedarikçi ekranları:** liste (`/tedarikciler`) + **detay** (`/tedarikciler/{id}`). Liste "kimden alıyoruz"u, detay "bu otel ne teklif etti ve hangi anlaşmayla"yı cevaplıyor — ikisi de tüm geçmişi istiyor, satır altına sığmıyor. Düzenleme detayda: tedarikçi **düzenlenir, yeniden oluşturulmaz** — fiyat geçmişi ve anlaşmalar o kayda bağlı, ikinci kayıt tarihçeyi ikiye böler.

⚠ **Hizmet adı serbest metin kalıyor** *(Sets kararı, 30.07.2026)*: "akıllıca kullansınlar, biz düzgün gösterelim". Bölgesel tarihçe bu alana göre gruplandığı için "konaklama" ve "Konaklama" ayrı tarihçe olur; bilinen ve kabul edilen sınır. Yönetilen listeye çevirmek ileride konuşulur.

**Tedarikçi anlaşmaları:** `supplier_agreements` — geçerlilik tarihleri, indirim oranı, şartlar; imzalı kopya `supplier_documents` (AD-24 Ek 2 deseni, kendi tablosu çünkü tedarikçinin tenant'ı yok). İndirim **anlaşmanın içinde** yaşıyor, tedarikçi satırında değil: soru geçmiş zamanlı — *"x otelde indirimimiz neydi"* — ve tek bir alan yalnız bugünü cevaplar. Yeni anlaşma eskisini düzenlemez, satır ekler. ⚠ `price_lists.discount_percentage` ile karıştırılmamalı: o **müşteriye verdiğimiz**, bu **tedarikçinin bize verdiği**.

**Tedarikçi türü:** `OptionList::SUPPLIER_KIND` (`TEDARIKCI-TURU`) — backoffice → Seçenek listeleri altından yönetiliyor, enum değil (AD-08). Tohum: konaklama, araç kiralama, seyahat acentesi, turizm acentesi, yemek/ikram, toplantı-eğitim salonu, ekipman kiralama, matbaa, diğer.

### Mevzuat
Kayıt kural değil **uyarı** üretir; sayılar eğitim tanımının alanıdır. "MEVZUATTA YOK" da bir uyarıdır. Zincir (lex specialis) otomatik çözülmez, uyarı olarak yaşar. → AD-27, `mevzuat-kurallari.md` §11

### Ofis uçtan uca denetimi — tarayıcıda *(08.08.2026)*

Satışçı, operasyon direktörü ve eğitmen olarak panelde gezildi. **Her bulgunun yanında geçen bir test duruyordu.**

⛔ **Yasal onay ekranı hiç sözlük göndermiyordu** — başlık, onay kutusu, "Onayla" butonu, hepsi ham anahtar. `RequireLegalConsent` her ofis rotasının önünde durduğu için bu her yeni hesabın gördüğü **ilk ekran**; onay kanıtımız kişinin okuyamadığı bir sayfayı kabul etmesi olacaktı.

⛔ **Görev ekranının iki eylemi de 404'tü** — `/yonetim/gorevlerim/...` diye POST ediyordu, rota `/gorevlerim/...`. Evrak istenen eğitmen belgesini yükleyemiyordu.

⛔ **Planlama gündemi `/talepler`'e bağlıydı** (yok; doğrusu `/planlama/talepler`), satır butonu ise hiç yazılmamış bir detay ekranına.

⛔ **Hata sayfaları rotasız adreslerde çevrilmiyordu** — yönlendirme `web` grubundan önce patlıyor, `share()` hiç koşmuyor.

⛔ **Tarayıcı çevirmeni `:total` içindeki `:to`'yu değiştiriyordu** → "1tal görev". Sunucu tarafı hep doğruydu; hiçbir test farkı göremezdi.

### Turun kapanışı — dört bulgu daha *(08.08.2026 akşamı)*

⛔ **Onaylı masraf geri alınamıyordu** *(Sets)*: *"insan hatası olasılığını 0'a indirerek sistem kuramayız."* Tetikleyici kaldırılmadı, daraltıldı — `approved → submitted` serbest, doğrudan karşı karara geçiş hâlâ yasak. **Ödenmişse kapalı** (`instructor_payment_allocations`); açılırken eğitimin maliyet kalemi de geri sarılıyor. Tam metin: `docs/issues.md`.

⛔ **Yeniden açılan karar geçmişte "onaylandı" görünüyordu** ve yeniden aç butonunu göstermeye devam ediyordu. Geçmiş iz kaydını okur; *yeniden sorulabilir mi* sorusu **kaydın bugünkü hâline** aittir (`Support\Approvals\DecisionState`).

⛔ **Eğitim günü UTC'de başlıyordu.** `09:00` duvar saati değeridir; üç yer onu UTC'de kuruyordu, yani her program üç saat geç yazılıyor ve yoklama penceresi dersten üç saat sonra açılıyordu. Tek erişimci: `Settings::trainingDayBeginsOn()`.

⛔ **Bir test dosyası başka bir test dosyasının yardımcısını çağıramaz** (EGT-75'in bir kat yukarısı). `--filter` ile geçiyor, grup olarak koşunca ölüyordu; altı çağrı vardı, arch testi artık reddediyor.

### Sıradaki işler *(Sets, 08.08.2026)*

Sırayla: **1)** CI koşusu ve müşteri paneli turunun kapanışı · **2)** ürün landing sayfası (`design/L01 Landing Ana Sayfa.dc.html` — ⚠ *"ayrı repoda yayınlanır"* diyordu, **yanlıştı**: bu repoda, `resources/views/landing.blade.php`) · **3)** ofis için **Yardım Merkezi** (`design/Yardim Merkezi.dc.html`).

⚠ Onaylanmış ama başlanmamış iki kalem: teklif isteği bildirimleri (3E'ye "istek geldi", firma onaylayıcısına "iç onay bekliyor") ve **katılımcı alanı turu** (`/katilimci`, `/anket`).

### Müşteri paneli turu *(08.08.2026 gecesi)* — `docs/reviews/musteri-paneli-turu-2026-08-08.md`

⛔ **Ray rozeti başka firmaların tekliflerini sayıyordu.** `quotes` bilerek RLS taşımaz (kapsam sorguda `customer_id` ile kurulur) ve rozet bunu unutmuştu. Testi vardı ve **düşemezdi** — fixture tek firma yaratıyordu.

⛔ **İç onay akışı hiç çalışmıyordu:** anahtar 500 veriyordu (reddediş `InvalidArgumentException`'dı), onay verilemiyordu (`TrainingRequest` rotada bağlanıyor ama `ResolvedWithinTenant` taşımıyordu), isteyenin adı boştu (`morphTo('opened_by')` ilişkiyi `opened_by` adlandırıyor, `with('openedBy')` başka kovaya yükleniyordu).

⛔ **Sabit `null` dönen kartlar:** panonun iki teklif kartı ve personel listesinin üç kolonu, bekledikleri dikeyler yazıldıktan çok sonra hâlâ "veri henüz yok" diyordu. Dört durumlu uyum kuralı tek yere alındı (`Domain\Customers\ComplianceState`).

⛔ **Panel adresleri token'a geçti** *(Sets kararı)*: `/teklifler/2` değil `/teklifler/<token>`. Çözüm `PortalScreen::fromToken()`'da; modele binding konmadı — teklif ve eğitim 3E'nin kayıtları ve ofis onları bugün id ile adresliyor. ⚠ Form değeri olarak taşınan id'ler (paket, çoklu atama) ayrı bir tur.

### Sets'in beş kararı *(08.08.2026)*

0. ⛔ **`discount.approve` diye bir izin hiç var olmamış** — gerçek ad `discount_limit.approve` ve `OfficeNavigation::APPROVAL_RIGHTS` yazıldığı günden beri yanlış yazıyordu. **Satış yöneticisi bu listede yalnız bu hakkı taşır**, dolayısıyla `/onaylar` ve `/yonetim` ona kapalıydı: kendi kararı olan tek şeyi — indirim tavanını aşan teklifi — açamadığı bir ekranda veriyordu. Direktör diğer haklarından girdiği için kimse fark etmemiş.

1. **İniş konuma göre ayrışır.** Operasyon direktörü `/yonetim`'e, satış yöneticisi `/satis`'e, **muhasebe `/muhasebe`'ye** (muhasebe ekranı tüm masraf ve teklifleri gösterir; satış en fazla kendi sorumlu olduğu teklifi görür). ⛔ İniş **hiç kimseden hak almaz**: direktör indirim dahil her şeyi onaylar ve satış yöneticisinin kararını geri alabilir; `OPERATIONS_RIGHTS` yalnız ön kapının hangi ekrana açıldığını söyler. Sıra "en genişten" değerlendiriyordu ve direktör teklif yetkisi de taşıdığı için satış kokpitine düşüyordu. ⚠ `RoleLandingTest` bunu test ediyor **ve geçiyordu** — icat ettiği direktör teklif/eğitim yetkisi taşımıyor, gerçek direktör taşıyor. `discount.approve` bilerek operasyon hakkı sayılmadı: indirim onaylamak satmanın parçası.
2. **Index'ten detaya satır tıklamasıyla gidilir**, menüden değil — menü sürtünme yaratır. `DataTable` artık `row-href` alıyor; yedi ofis listesine bağlandı. Ad da gerçek bir link (orta tık, kopyala, ekran okuyucu).
3. **Markdown gösterilir.** Yasal metin, sözleşme metni ve müşterinin onayladığı teklif hep markdown editöründe yazılıyor ve düz metin olarak gösteriliyordu. `Support\Text\Markdown` ham HTML'i **siler**; `QuoteVersion` hash'i kaynak metnin üzerinde kalır (AD-14).
4. **Görev satırı hangi kayıt olduğunu söyler ve oraya götürür** — bildirim rayının kullandığı çözücüden, yani açılamayacak adres sunulmaz. Ad, adres olmasa bile gider.
5. **Satışçı planlamacının gündemini göremez.** Orası planlamacının BI ekranı: gelen talepler, revize istekleri, planladıkları, onaya gönderdikleri. Satışçı yalnız kendisine atanmış planlanmış eğitimi görür.

⛔ **Operasyon direktörü ofisin süper adminidir** *(Sets'in tekrarlanan uyarısı)*. Yetkisi olduğu sürece eğitmene dair olan dahil her şeyi görür; eğitmen ayrı bir dünya değildir. Bir ekranın kapısını yazarken soru "hangi rolde" değil **"bu izne sahip mi"**dir.

### ⛔ Hiçbir log otomatik silinmez *(Sets, 07.08.2026)*

Saklama yükümlülüğü **10 yıla kadar** uzuyor. Hangi logun hangi yükümlülüğe cevap verdiği yıllar sonra sorulan bir soru — sistemin bir gece chunk düşürerek önden karar vereceği bir şey değil. Yaşlanıp gitmiş bir log üretilemez ve hiçbir ayar onu geri getirmez.

⚠ **Bir tanesi koşuyordu.** `audit_usage` üzerinde TimescaleDB retention policy: 730 günden eskiyi her gün düşürüyordu, `timescaledb_information.jobs` içinde tek satır. Zamanında gerekçelendirilmişti (*"kullanım logu delil değil, bu disk temizliği"*) — gerekçe yanlış şeyi tartışıyordu. `2026_10_02_000100_no_log_disappears_on_a_schedule` kaldırdı; `AUDIT_USAGE_RETENTION_DAYS` üç env şablonundan ve config'ten silindi (okunmayan ayar, birinin bir gün geri bağlayacağı ayardır).

⚠ **Bilerek silmek başka.** `PurgeUsageLog` bir backoffice eylemi: kişi neyin gideceğini söyler, izni vardır, audit satırı bırakır. Fark DELETE değil, **karar veren biri olup olmadığı**.

⛔ **Neden iki kontrol var:** S-1 zaten *"süre dolunca silen job yok"* diye tarıyordu ve **koşan policy'yi görmemişti** — çünkü o bir migration'daki `add_retention_policy(...)` çağrısıydı, TimescaleDB'nin kendi zamanlayıcısına devredilmişti. Hiçbir PHP koşmuyordu, hiçbir sınıf o adı taşımıyordu. Bulunma şekli koda bakmak değil, **veritabanına sormak** oldu — o yüzden ikinci kontrol iş kataloğunu okuyor (`Feature/Omurga/DatabaseInventoryTest`).

### ⏸ Pazarlama ve İYS kapsam dışı *(Sets, 07.08.2026)*

Bu sistem üzerinden pazarlama yapılmıyor: gönderilen her şey işlemsel — teklif, şifre hatırlatma, magic link, görev bildirimi. Ticari elektronik ileti altyapısı **yok**, dolayısıyla İYS'ye bildirilecek onay/ret de yok. Yapılırsa **planlanacak**.

⚠ Spec §14.11'in ikinci yarısı yürürlükte ve kanıtlı: bounce sonrası adres otomatik gönderim dışına alınıyor — işlemsel posta da geri döner.

### Kural taraması — S-1…S-19 arch testi oldu *(Sets, 07.08.2026)*

Planın `deployment/scripts/rule-scan.sh`'i **yazılmadı**; on dokuz kural `tests/Arch/` altında koşuyor. Gerekçe: suite zaten CI'da koşuyor, ikinci bir tarayıcı aynı allowlist'lerin ikinci kopyası olurdu — ve bayatlayan kopya, kimsenin bakmadığı kopya olurdu. Planın istediği "bulgu build'i kırar" şartını düşen bir test zaten karşılıyor.

**Nerede:** on üçü `tests/Arch/RuleScanTest.php`; altısı daha önce yazılmıştı — S-1/S-7 `SensitiveDataTest`, S-4 `TenancyTest`, S-5 `EnvTest`, S-8 `NotificationChannelTest`, S-10 `LocaleTest`.

⚠ **Taramalar yorumu silerek okur.** Ölçüldü: audit tablosu adı geçen otuz dosyanın on dokuzu adı *"burayı okumuyoruz çünkü…"* cümlesinde anıyordu. Açıklamayı cezalandıran bir tarama, açıklamasız kopyayı geçirir ve bir hafta içinde kapatılır.

⚠ **Her kural kasıtlı ihlalle doğrulandı** — yakalamayan kural kural değildir. S-6 ilk yazımında **yakalamıyordu**: desen `token` kelimesinden hemen sonra `=>` bekliyordu, oysa gerçek kod `'token' => '…'` yazar; tırnak arada duruyordu. Gerçekçi bir sahte anahtar ekilip taramanın geçtiği görülmeseydi kural sessizce boş kalacaktı.

**Üç sapma onaylandı** *(Sets, 07.08.2026)* ve planın §9.5'ine işlendi:

- **S-11'in izinli ikinci dosyası `AuditPolicy`** — plan chunk3 migration'ını yazıyordu; politika SQL'i o migration'dan çıkıp sınıfa taşınmış, migration sınıfı çağırıyor. Aynı turda sınıfın çağıranı olmayan iki `public` metodu `private` yapıldı: bayrağı iki dosyaya kapatmanın değeri, o dosyalara açılan kapı sayısı kadardır.
- **S-12/S-13 "dizge geçmesin" değil "sorgu başlatmasın"** — altı ekran feed'in verdiği satırları `AuditWrite $row` diye tipliyor; tip ipucu veritabanına ulaşmaz. Planın harfi insanlara sorguyu değil tipi silmeyi öğretirdi.
- **S-17 `original_filename`'i kapsamıyor** — `storage_path` bir **adres** ve `app/Http` ile `routes/` altından tamamen çıktı; dosya adı ekranda yazan bir **etiket** ve orada kalır.

⏳ **Karantina mekanizması bilinçli olarak yazılmadı** *(Sets, 07.08.2026)*. Bugün tutarsız test yok; iskelenin şeklini ilk gerçek vaka söyler. Kapanış adresi plan §9.4'te. ⚠ Bugüne kadarki tek "tutarsızlık" test değildi: `omurga-rls` aynı veritabanına paralel başka bir koşum varken toplu düşüyor, tek başına 333/333 geçiyor — karantina vakası değil, koşum kuralı.

### Kabul kriterleri artık kendini kolluyor *(Sets, 07.08.2026)*

Spec §14'ün 38 kriteri **doküman değil, koşan bir kontrol** (`tests/Arch/SpecCriteriaTest.php`). Kriter listesi spec'ten okunuyor, kanıtlar testlerin yanındaki `// spec omurga §14.N` işaretinden toplanıyor; ikisi de yerinden oynayınca build kırılıyor.

⚠ **İşaret merkezî bir tabloda değil, testin bir satır üstünde.** Testi yeniden adlandırmak iddiayı da taşır, testi silmek iddiayı da götürür, testi okuyan neyin kanıtı olduğunu görür. Merkezî liste, tam da kimsenin bakmadığı testlerle yoldan çıkardı.

⛔ **Kontrolün kanıtlamadığı şey:** işaretli testin kriteri gerçekten kanıtlayıp kanıtlamadığı — bu makineyle denetlenemez, review'ın işidir. Kapattığı şey **sessiz kayıp**: kanıtı silinen, adı değişen ya da hiç yazılmamış kriter.

**Kanıtsız kriter kalmadı.** Tek istisna **§14.19** ve o ayrı bir kategoride: *kapsam dışı*. ⚠ İki kategori bilerek ayrı — *"henüz yapılmadı"* birinin ödemeyi düşündüğü bir borç, *"yapılmıyor"* bir karar; tek listede tutmak kararı borç gibi okutur, ödenmeyen borcu da zamanla karar gibi.

⚠ **§14.8 ve §14.11 listeden kararla düştü**, yapıldıkları için değil: logların otomatik silinmemesi artık kanıtlı (iki kontrol), promosyonel gönderim ise kapsam dışı.

⛔ **§14.16 hiç eksik değildi** *(Sets, 08.08.2026)*. Listeye *"davet bazlı eğitim hiç kurulmadı — kolon yok, sorgu yok, ekran yok"* diye yazmıştım; bu, `invitation` kelimesini arayıp bulamamaktan çıkarılmış bir sonuçtu. Sets bariz olanı söyledi: **kapalı eğitim zaten firmalarını sayıyor**, adı geçmeyen firma onu ne listede görür ne kodla erişir — planlama kapalıysa müşteriler seçiliyor, teklif gidiyor, onaydan sonra personel ekleniyor. Dört test kanıtlıyor ve ikisi aylar önce yazılmış (`TrainingVisibilityTest`, `VisibilityRuleTest`, `TrainingPolicy`, `ReferenceCodeTest`). **Sonuçsuz bir grep yokluğun kanıtı değildir** — kayıt böyle kalsaydı biri ürünün zaten sahip olduğu bir özelliği yazmaya gidecekti.

⚠ **§14.14 kısmî:** kanıtı, anonimleştirme scriptinin production'ı reddettiği (`Arch/SensitiveDataTest`) — mekanizmanın bekçisi, "prod dışı veri maskeli" olgusunun kendisi değil.

### RLS grubu 66 sn → 43 sn *(07.08.2026)*

Emilen şey testler değildi. `DatabaseTruncation` tabloları tek tek dolaşıp her biri için önce `SELECT exists(...)` sorup sonra kendi `TRUNCATE`'ini atıyor — 176 tablo, tek satır silinmeden önce 176 gidiş-dönüş, **her testten önce**. Ölçüldü: hepsinin tek seferde truncate'i **85 ms**. Maliyet silmek değil, 333 kez bunun muhabbetini yapmaktı.

`Tests\Concerns\TruncatesDatabase` aynı işi iki gidiş-dönüşte yapıyor. ⛔ **Ne boşaltıldığı değişmedi:** tablo seçimi yine framework'ün `tablesToTruncate`/`exceptTables` kancalarından, kimlik sayaçları yine sıfırlanıyor. Yalnız kaç cümlede söylendiği değişti — Sets'in şartı buydu, *"orada hız değil doğruluk önemli"*.

⚠ Grubun en yavaş tek testi hâlâ 7,6 sn (`it never issues one code twice`, 10.000 kod). Küçültmek testi zayıflatmak olur; bütçe zaten karşılandı, gereği yok.

### Test
Grup etiketi zorunlu: `omurga` · `omurga-rls` · `backoffice` · `planlama` · `satis` · `egitmen` · `musteri` · `katilimci` · `entegrasyon` · `mevzuat`. Çıplak `now()` yasak, retry ile yeşile geçirme yasak. Geliştirmede lokal koşu; **TeamCity yalnız ticket kapanışında**. → AD-26, CLAUDE.md

---

## 6. Kırmızı çizgiler

Tam liste `CLAUDE.md`'de. En sık unutulanlar: tenant bağlamı session-level `SET` (AD-04) · `env()` config dışında · hardcoded iş kuralı veya mevzuat sayısı (AD-08, AD-27) · migration'sız DDL · kritik kayıt hard delete (AD-11) · audit'siz kritik işlem · kod yerleşimi ihlali (AD-25) · commit'te AI attribution.

---

## 7. Ticket haritası

| Aralık | Ne |
|---|---|
| EGT-1…EGT-19 | Faz 0 — altyapı, mevzuat çalışması ✓ |
| EGT-20…EGT-28 | Faz 1 — spec yazımı ✓ **(sekizi de kapalı)**. EGT-23 10.08.2026'da kapandı: spec turu atlandı, iş doğrudan uygulandı — on iki kalem koda karşı ölçüldü, açık kalan tek satır (*benden / genel onay kuyruğu*) aynı turda yazıldı |
| EGT-29…EGT-37, EGT-52, EGT-53 | Faz 2 — omurga chunk'ları (1–8, 10 ✓; **yalnız 9 açık**). EGT-37 ve EGT-35 07.08.2026'da kapandı |
| EGT-38 | Faz 3 — dikey modüller ✓ (09.08.2026, üretimde) |
| EGT-39…EGT-51 | Backlog, entegrasyonlar, production hazırlık. **EGT-51 kapandı** (10.08.2026): dört kalemin ikisi tamam, biri *(hukuk teyidi)* **düşürüldü** — Sets: *"bizim işimiz değil"* —, biri **EGT-92**'ye ayrıldı |
| EGT-54…EGT-56, EGT-59 | Ofis ön yüz turları ✓ |
| EGT-57 | Eğitmen spec ✓ |
| EGT-58, EGT-60 | Açık düzeltmeler |
| EGT-71 | Tasarım hizalaması — `61`, `25`, `63`, `40` — ✅ `Fixed` (CI #926). Dördü ölçüldü; `61`'de dört boşluk kapandı, `DataTable` **dilim satırı** yeteneğini kazandı. ⚠ Ticket "23" diyordu, asıl dosya **`25`** |
| EGT-72 | Finans ve onay veri katmanı (In Progress) |
| EGT-73 | Omurga kökü — zincirin tamamı yazılı (personel/lokasyon → katılımcı → yoklama/sınav → günü kapat → tamamlama → hakediş → cari). 09.08.2026'da ölçüldü; açık kalan tek yer `12`'ydi, üç eksiği de kapandı |
| EGT-74 | Müşteri portalı ve teklif dikeyi — 48/48 kriter ✓, CI yeşil; ⏸ kapatma kararı Sets'te |
| EGT-76, EGT-79 | Kapsam kararı bekleyenler — D-6 (İYS/SMS), D-16. ⚠ **EGT-79 tekrar rafa kaldırıldı** *(Sets, 10.08.2026: "kapsamlı bir iyileştirme yapmak gerekecek galiba")*; üç eksiği de duruyor, dördüncüsü ölçümde çıktı: `musteri_firma` kapsamında **tanımlı tek belge türü yok** |
| **EGT-92** | Audit zincirinin bağımsız doğrulama aracı — EGT-51'den ayrıldı. Backoffice'e konacak; ⚠ *"tool koymak tüm sistemi şeffaflaştırmak demek"*, yapısı Sets tarafından planlanacak |
| EGT-87 | Postmark açılma takibi — ⛔ **obsolete kapatıldı** (10.08.2026): *yapıldı değil, yapmıyoruz*. Okuma tarafı hazır bekliyor (`RecordLetterOpened`), gönderim başlığı hiç yazılmadı |
| EGT-88, EGT-89, EGT-90 | ✅ (10.08.2026) — 27 makalenin hepsi ekran görüntülü; changelog hatırlatıcısı **Stop hook** oldu; NDA gizlilik metni zaten legal sette |
| **EGT-78** | Mezuniyet muafiyeti motoru (D-15) — ✅ (09.08.2026): kapalı küme seçenek listesinde, muafiyet kaydı madde referanslı, uyarı iki ekranda, eğitmen ön şartı artık belgenin İÇİNE bakıyor |
| **EGT-77** | 3E'nin kendi yetki belgesi (D-13) — ✅ (09.08.2026): kurum belge kapsamı, `/yonetim/kurum-belgeleri`, yenileme görevi (Y-2016 md.12: 3 ay önce) |
| **EGT-88, EGT-89** | Yardım Merkezi (26 makale + 8 soru) ve Yenilikler — ✅ (09.08.2026) |
| EGT-80 | Sözleşmeler — müşteri tarafı yazıldı (09.08.2026): onay kapısı, `/sozlesmeler`, geri çekme, sürüm farkı, teklif onayında çerçeve sözleşme. ⏸ Matriste yedi soru + video rızası bağlanmadı |
| **EGT-82** | Ticari sözleşmeler — çerçeve sözleşme uçtan uca ✅ (09.08.2026): elektronik kabul, ıslak imza, antetli çıktı, üç yükleyici |
| EGT-90 | NDA — motor hazır, metin ve akış yok; karar bekliyor |
| **EGT-91** | `office_agreements` bacağı — ✅ (09.08.2026): gizlilik taahhütnamesi kapıda, istihdam sözleşmesi belge + task |
| **EGT-81** | Satışçının imzalı nüsha yükleme ekranı — ✅ yazıldı (09.08.2026). Tasarımcı beklenmedi; gerekçe ve matris `docs/reviews/brief-a5b-kapsama.md` |
| **EGT-83** | Tablo borcu + tasarım `31`/`22`/`37` hizalaması — ✅ `Fixed` (CI #922, #923) |
| **EGT-84** | Ofis passkey ekleme akışı backoffice ile aynı şekle getirildi (modal) — ✅ `Fixed` (CI #925). ⛔ Müşteri panelinde passkey **hiç yok**, kapsam dışı bırakıldı (Sets, 07.08.2026) |

| **EGT-190…EGT-197, OPS-23…OPS-25** | Tayfun'un canlı testinden gelen düzeltmeler *(22.08.2026)*. Kapananlar: backoffice daveti gerçekten gönderiliyor (EGT-195/OPS-24 — çerçevenin hazır bildirimi olmayan bir rotaya bakıyordu), sözlükteki çift başlık (EGT-194), saatlerin çeyreğe kadar girilmesi + müfredat penceresinin Türkçeleşmesi + tarih biçimi (EGT-196), panel kartının fiyat bandı ekranına bağlanması (EGT-197). ⚠ **EGT-197'nin ölçümü karar bekliyor:** canlıda «Görev 8 — Yükleme/istif planları» 24 saat, en uzun bant 20 saat; eşleşmeyen eğitim fiyatsız kalmıyor, **en uzun banttan** fiyatlanıyor. Ya 24 saatlik bant eklenecek ya eğitimin saati düzeltilecek |
| **Sentry envanteri** *(22.08.2026)* | Organizasyon paylaşımlı (puanbenim, inzayede aynı yerde). 3e-egitim'in 24 açık kaydından 20'sinin OPS ticket'ı vardı; ticket'ı `Fixed` olanların Sentry kaydı **kapatılmıyordu** — 17 kayıt resolved edildi. Ticketsiz kalan tek production kaydı **OPS-25** olarak açıldı (`bootstrap/cache` yazılamaması, 11.08 dosya sahipliği kazası). ⛔ Akışa eklenmesi gereken adım: ticket kapanırken Sentry kaydı da kapanır |

---

## 8. Açık sorular

Tam liste `docs/issues.md`'de. Karar bekleyen başlıklar:

| Konu | Kimde |
|---|---|
| "En az 3 katılımcı" — mevzuatta yok, operasyon/bakanlık teyidi | Sets |
| e-Fatura entegratörü seçimi | Sets |
| Audit zincirinin delil sınıfı — nitelikli zaman damgası zorunlu mu | Hukuk teyidi, EGT-51[3] |
| Backoffice tehdit varsayımı — şirket sahibi olmayan taraf girerse | Yeniden değerlendirme, rol ailesi genişleyince |
| `analytics_usage_daily` çapraz-tenant okuma mekanizması | chunk 8 turu |
| **Dağıtım scripti yok** — `AuthorizationSeeder` "her deploy'da koşar" diyor ama koşturan bir mekanizma yazılmadı. İzin satırı eksikliği artık `PermissionRows` ile kendini onarıyor, yani sessiz kaybı yok; ama option list, evrak türü, mevzuat seeder'ları hâlâ elle koşuyor. | Production hazırlık turu (EGT-39…EGT-51) |
| Resmî tatil takvimi kaynağı | EGT-43 |
| Fiyatlarda KDV/para birimi davranışı | Teklif/fatura spec'i |

---

## 9. Revizyon geçmişi

| Tarih | Değişiklik |
|---|---|
| 29.07.2026 | İlk sürüm. Sekiz alan, çapraz konular, planlama turunun kararları. |
| 29.07.2026 | Planlama kararları `planlama-spec.md` v1.0'a yazıldı; harita tam metinden adres tablosuna döndü. |
