# EGT-66 — Eğitmen paneli: teklif, kabul, atamalarım

**Upstream:** `docs/specs/egitmen-spec.md` §3 (uygunluk), §6.2–6.13 (atama zinciri) — heyet 158 **APPROVED**, kilitli · AD-20 (mailden aksiyon yok), AD-21 (erişim logu), AD-23 (kapsam), AD-25, AD-26 · `design/40 Egitmen Paneli.dc.html`

**Baseline:** `main` @ CI #834 yeşil (1147 test)

---

## Neden bu tur

⛔ **Bugün sistemde kapanmayan bir halka var.** Planlamacı eğitmen isteği açıp teklif gönderebiliyor (`SendInstructorOffers` ✓), teklifin süresi saatlik işle doluyor (`ExpireInstructorOffers` ✓), ama **teklifi kabul edecek bir ekran yok**. `AcceptInstructorOffer` ve `DeclineInstructorOffer` yazılı ve çağıransız; yani bir eğitim uçtan uca kadroya bağlanamıyor.

Bu tur o halkayı kapatır. Spec zaten onaylı — spec turu yok.

---

## Sözleşme — spawn öncesi pinlenir

| Şey | Ad |
|---|---|
| Rota öneki | `/egitmen` (ofis host'u, ofis kimliği) |
| Ekran | `Office/Instructor/Dashboard`, `Office/Instructor/Offer`, `Office/Instructor/Assignment` |
| Action | `DeclineInstructorOffer` (var, imza genişler), `AcceptInstructorOffer` (var) |
| Query | `MyInstructorOffers`, `MyAssignments`, `OfferDetail`, `AssignmentDetail` |
| Yeni izin | **yok** — kimlik yeter (aşağıda) |
| Test grubu | `egitmen` |

---

## Görev 1 — Kimlik: eğitmen kendi kaydını nasıl bulur

**Dosyalar:** `app/Queries/Office/MyInstructorProfile.php`, `app/Http/Middleware/HandleInertiaRequests.php`

⛔ **Yeni izin açılmaz.** Eğitmen bir ofis kullanıcısıdır (§1) ve gördüğü şey **kendi** kaydıdır; `instructor.view.own` gibi bir izin, sahipliği zaten belirleyen bir soruya ikinci bir cevap üretirdi. Kapı: `users.id → instructors.user_id`. Eğitmen kaydı olmayan kullanıcı `/egitmen` altında **404** alır — 403 değil, çünkü onun için o ekranlar yoktur.

**Adımlar:**
- [ ] `MyInstructorProfile::for(User)` → `?Instructor`
- [ ] Sidebar'a "Eğitmen" girişi: yalnız eğitmen kaydı olana (`OfficeNavigation`)
- [ ] Test: eğitmen olmayan 404 · eğitmen olan görür · başkasının teklifi 404

---

## Görev 2 — Bekleyen teklifler ve kabul kapısı

**Dosyalar:** `app/Queries/Office/MyInstructorOffers.php`, `app/Http/Controllers/Office/InstructorPanelController.php`, `resources/js/pages/Office/Instructor/Dashboard.vue`

⛔ **Kabul öncesi/sonrası bilgi kapısı (§6.9).** Teklif ekranı yalnız kararı vermeye yetecek kadarını gösterir: eğitim adı ve kapsamı, tarih, yer, süre, **kendi ücreti**, katılımcı **sayısı**. Otel/bilet, dokümanlar ve **katılımcı listesi kabul sonrasıdır** — kabul etmemiş bir eğitmenin müşterinin kişisel verisini görmesi için sebep yok.

⚠ Kendi ücreti görünür, **diğer adayların ücreti görünmez**. `selection` modunda karşılaştırmayı operasyon yapar; adayın rakibinin fiyatını görmesi pazarlık verisidir, karar verisi değil.

⚠ Süre **her teklifin kendi gönderim anından** işler (§6.7); ekran kalan süreyi teklif başına gösterir.

**Adımlar:**
- [ ] `MyInstructorOffers::pendingFor(Instructor)` — kapı öncesi alanlar
- [ ] Kokpit ekranı: bekleyen teklifler + yaklaşan atamalar
- [ ] Test: kabul öncesi katılımcı listesi payload'da **yok**, sayı var · başka adayın ücreti yok

---

## Görev 3 — Kabul ve ret

**Dosyalar:** `app/Actions/Office/AcceptInstructorOffer.php` (değişir), `DeclineInstructorOffer.php` (değişir), `app/Http/Requests/Office/DeclineOfferRequest.php`

⛔ **Ret gerekçesizse kabul edilmez (§6.6).** Gerekçesiz ret bir boşluktur; gerekçeli ret operasyonun gördüğü sinyaldir — aynı eğitmenin üçüncü kez aynı sebeple reddetmesi ancak böyle görünür.

⛔ **Kabulde uygunluk YENİDEN değerlendirilir (§6.3).** Teklifle kabul arasında belge düşmüş olabilir. Çakışma kaynağı yalnız **kesinleşmiş** atamalar — bekleyen bir kabul kimseyi bağlamaz (§6.5), onu engel saymak kuralın kendisini delerdi.

⚠ Kabul zaten atomik ve kilitli (`AcceptInstructorOffer` ✓). Bu görev ona **uygunluk kontrolünü** ve panelden çağrılan yolu ekler.

⚠ Mail yalnız çağırır, karar panelden verilir (§6.13, AD-20). Bu turda mail gövdesine dokunulmaz; bildirim altyapısı chunk 7.

**Adımlar:**
- [ ] `AcceptInstructorOffer`'a kabul anı uygunluk kapısı
- [ ] `DeclineInstructorOffer` gerekçe zorunlu
- [ ] Rota: `POST /egitmen/teklifler/{offer}/kabul` · `/ret`
- [ ] Test: gerekçesiz ret reddedilir · belgesi düşmüş eğitmen kabul edemez · kesinleşmiş çakışma kabulü reddeder · bekleyen kabul engel değildir

---

## Görev 4 — Yeniden onay (`reconfirming`)

**Dosyalar:** aynı action'lar, `MyInstructorOffers::reconfirmingFor()`

⚠ Tarih/yer değişince atama `reconfirming` oluyor (`ReconfirmAssignments` ✓ — planlamacı tarafı yazılı). Eksik olan **eğitmenin cevap verebileceği yer**.

⛔ Yenileme reddedilirse atama **düşer** ve §6.7'nin yükseltmesi devreye girer. Eğitim satılmışsa bu bir **ALERT**'tir: müşteriye taahhüt edilmiş bir eğitimin eğitmeni yoktur; operasyon + eğitim direktörü + **satış** bilgilendirilir.

⚠ Satış modülü yok — alert'in satış ayağı **görev üretmekle** karşılanır, adresi plana yazılır.

**Adımlar:**
- [ ] Yeniden onay kartı kokpitte, kabul/ret aynı yoldan
- [ ] Ret → atama düşer, görev açılır
- [ ] Test: reddedilen yenileme atamayı düşürür · satılmış eğitimde alert görevi doğar

---

## Görev 5 — Atama detayı: kabul sonrası bilgi

**Dosyalar:** `app/Queries/Office/AssignmentDetail.php`, `resources/js/pages/Office/Instructor/Assignment.vue`

Kabul sonrası: eğitim dokümanları, yer/ulaşım notu, **katılımcı listesi**.

⛔ **Katılımcı listesine erişim loglanır** (AD-21, kırmızı çizgi 13). Hassas kayda dokunan her OKUMA loglanır; müşterinin personel listesi tam olarak odur.

⚠ Katılımcı tablosu bugün boş (satış/katılımcı turu). Ekran listeyi **var olduğunda** gösterir, yokken "henüz katılımcı yok" der — uydurma veri yok.

**Adımlar:**
- [ ] Atama detayı + erişim logu
- [ ] Test: kabul etmemiş eğitmen detayda katılımcı göremez · okuma audit'e düşer

---

## Görev 6 — Müsaitlik: eğitmenin kendi bildirdiği izin

**Karar (Sets, 30.07.2026):** eğitmen izin günlerini girebilmeli; girdiği gün aday listesinden düşer.

**Dosyalar:** `database/migrations/..._create_instructor_unavailability.php`, `app/Models/InstructorUnavailability.php`, `app/Actions/Office/DeclareUnavailability.php`, `app/Queries/Office/EligibleInstructors.php` (değişir)

⛔ **Uygunluğun ÜÇÜNCÜ kaynağı.** Spec §3.1 ikisini sayıyor — mevzuat (belge, sınır, yetkinlik) ve çakışma (kesinleşmiş atama). Bu üçüncüsü: kişinin kendi bildirdiği yokluk. Aynı rejimde çalışır — planlamacıya sorulmaz, **uygulanır**: izinli eğitmen aday listesinde görünmez.

⚠ Sebep **opsiyonel ve gizli**. "Neden izinlisin" planlamacının sorusu değil; listede görünmeyen bir kişinin sebebini göstermek, §6.3'ün "neden yok" demeyi reddetme gerekçesinin aynısıyla yanlış olurdu. Sebep eğitmenin kendisi ve operasyon içindir.

⚠ **Geçmişe dönük izin engellenmez, ama kesinleşmiş atamayı düşürmez.** Bir eğitmen dün için izin girerse bu bir kayıt düzeltmesidir; kabul ettiği eğitimi iptal etme yolu değildir (o §6.10'un yolu).

**Adımlar:**
- [ ] Tablo: `instructor_id`, `starts_on`, `ends_on`, `reason` (nullable), `created_by` · `CHECK (ends_on >= starts_on)`
- [ ] `DeclareUnavailability` + kaldırma; audit
- [ ] `EligibleInstructors::available()` çakışan izni de sayar
- [ ] Kokpitte "Müsaitliğim": aralık ekle/kaldır, gelecek izinler listesi
- [ ] Test: izinli eğitmen aday listesinde yok · izin bitince geri gelir · sebep planlamacı payload'ında yok · kesinleşmiş atama düşmez

---

## Görev 7 — Eğitmen dosyası ve evrak görevleri (operasyon tarafı)

**Karar (Sets, 30.07.2026):** *"operasyoncu yüklemesi gereken evrakları task olarak verebilir."*

**Dosyalar:** `app/Http/Controllers/Office/InstructorController.php`, `app/Queries/Office/OfficeInstructors.php`, `InstructorFileDetail.php`, `resources/js/pages/Office/Instructors/{Index,Show}.vue`, `app/Actions/Office/OpenDocumentTasks.php` (wire-up)

⛔ **Bugün hiç ekran yok ve bu bir delik.** `OpenDocumentTasks` ve `InstructorDocumentStatus` yazılı, ikisinin de çağıranı yok: eğitmen kaydı açılıyor, kimseden kâğıt istenmiyor, ve kimse hangi kâğıdın eksik olduğunu göremiyor. Y-2016 md.13/j kadro değişikliğini 15 gün içinde bildirmeyi istiyor; eksik evrak listesi olmadan o bildirim hazırlanamaz.

⚠ İki yerden açılır: eğitmen kaydı **açılırken** (yönetici hangi kâğıtları istediğini seçer) ve **sonradan** dosya ekranından. İkincisi olmadan, bir belge süresi dolduğunda onu yeniden istemenin yolu yok.

⚠ Görev **sahibine** düşer (`OpenTask` ✓, dedup ✓) ve eğitmen onu mevcut görev ekranından yükler (`UploadInstructorDocument` ✓). Yani bu görev yeni bir yükleme akışı değil, var olanın **başlatıcısını** bağlıyor.

**Adımlar:**
- [ ] Eğitmen listesi: ad, kategori, belge durumu (eksik/dolmak üzere), atanmış eğitim sayısı
- [ ] Dosya ekranı: `InstructorDocumentStatus` çıktısı + "evrak iste" (tür seçimiyle)
- [ ] Kayıt açılışına `OpenDocumentTasks` bağlanır
- [ ] Yetki: `instructor.view` / `instructor.manage_documents` — vokabülere eklenir
- [ ] Test: görev sahibine düşer · aynı tür iki kez istenince tek görev · yükleme görevi kapatır · yetkisiz iste

---

## Görev 8 — Kapanış

- [ ] Wire-up: `AcceptInstructorOffer`/`DeclineInstructorOffer` artık production caller'a sahip — reverse-grep ile doğrula
- [ ] `proje-haritasi.md` §4.6 güncellenir, kapanan kapanış adresleri silinir
- [ ] Pint · PHPStan · gruplar · `--parallel` · TeamCity yeşil

---

## Bu turda YOK — adresi yazılı

| Ne | Neden | Adresi |
|---|---|---|
| Masraf girişi, cari | Mali tur (§7.6–7.8) | EGT-28 |
| Saha ekranı: yoklama, sınav, oturum devamı | Operasyon turu | EGT-27 |
| Belgelerim (eğitmenin kendi evrakı) | Zaten var — görev ekranından yükleniyor | — |
| Belgelerim listesi (eğitmenin kendi arşivi) | Görev ekranı ve dosya ekranı ikisini de karşılıyor | — |
| E-posta gövdesi / bildirim | Bildirim altyapısı | EGT-37 (chunk 7) |

⚠ **Spec'e eklenecek:** müsaitlik (§6) ve evrak görevi başlatıcısı (§7) `egitmen-spec.md`'de yoktu; Sets 30.07.2026'da ikisini de istedi. Tur bitiminde spec'e işlenir — kod specten önce gitmez, spec kodun arkasından kapanır.
