# Chunk 2 — Katılımcı omurgası, eğitimlerim, sertifika kaydı

*Index: `musteri-portali-plan.md` · Spec: §8, §9.4–9.5 · Tasarım: `15`, `16` (sertifikalar)*

Bu planın en çok kapı açan parçası: `41 Egitmen Saha`, hakediş (#49) ve `40`'ın kalan boşlukları da bunun üstüne biner.

⛔ **Bu chunk backend'i kurar, `41` ve `50` ekranlarını kurmaz** (K-21). Backbone'un caller'ı henüz yoktur ve bu **bulgu değildir** (onay kuralı 4 istisnası). **Kapanış adresi:** `41 Egitmen Saha` ve `50 Katilimci Paneli`, bir sonraki tur.

---

## 1. Migration'lar

### 1.1 `training_sessions`
`id` · `training_id` · `sequence` (1,2,3…) · `title` (nullable) · `starts_at` · `ends_at` · timestamps.
⚠ **Tenant'sız** — eğitim ofis verisidir (planlama-spec). Görünürlük eğitimin kendi kuralından gelir.
Benzersiz: `(training_id, sequence)`.

### 1.2 `training_enrolments` — `training_participants`'ın yerine
`id` · `tenant_id` · `training_id` · `customer_user_id` · `scope_label` (hangi görev/kapsam için kayıtlı) · `status` (`enrolled` \| `reserve` \| `withdrawn` \| `no_show`) · `from_reserve` (bool) · `enrolled_at` · timestamps.
⛔ `TenantPolicy::apply`. Benzersiz: `(training_id, customer_user_id)`.

⚠ Geçiş index §4'te. ⛔ Eski tablo **düşürülmez**.

⛔ **Ama eski tablonun ÜÇ CANLI OKUYUCUSU var ve üçü de bu chunk'ta taşınır** (heyet 174 F-P3) — taşınmazsa yeni kayıt alan katılımcılar **panel yeteneği alamaz**, çünkü `PanelAccess` boş kalan eski tabloya bakar:

| Okuyucu | Ne yapar | Ne olacak |
|---|---|---|
| `app/Queries/Customer/PanelAccess.php` | ⛔ **katılımcı yeteneğinin türetildiği yer** (omurga §3.0 U-1/U-2) | `training_enrolments`'a çevrilir |
| `app/Models/CustomerUser.php` | ilişki | ilişki yeni tabloya |
| `app/Policies/TrainingParticipantPolicy.php` | katılımcı kaydı yetkisi | `TrainingEnrolmentPolicy` olarak yeniden adlandırılır; eskisi **silinmez**, `AuthServiceProvider` kaydı yenisine döner |

⚠ `app/Models/TrainingParticipant.php` modeli **kalır** ama hiçbir yerden okunmaz — tablo düşene kadar (ayrı ticket).

⛔ **`no_show` bir statüdür, boşalma değil** (spec §5.3, çatı §153): iptal `withdrawn`'dır ve slot boşaltır; `no_show` eğitim günü işaretlenir ve **terfi akışını tetiklemez**.

### 1.3 `session_attendances`
`id` · `tenant_id` · `training_session_id` · `training_enrolment_id` · `present` (bool) · `marked_at` · `marked_by` + `marker_type` (polimorfik: eğitmen ya da ofis) · timestamps.
⛔ `TenantPolicy::apply`. Benzersiz: `(training_session_id, training_enrolment_id)`.
⚠ **Zaman damgası ve kim işaretledi zorunlu** (spec §8.2) — sonradan "kim işaretledi" sorusunun cevabı satırda olmalı.

### 1.4 `participant_declarations`
`id` · `tenant_id` · `training_session_id` · `training_enrolment_id` · `declared_at` · `source` (`magic_link` \| `panel`) · timestamps.
⛔ `TenantPolicy::apply`.
⚠ **İkincil kanıt** (spec §8.2). Yoklamanın yerine geçmez; ayrı tablo olmasının sebebi budur — aynı tabloda `is_declaration` bayrağı, ikisini aynı ağırlıkta gösterirdi.

### 1.5 `exam_results`
`id` · `tenant_id` · `training_enrolment_id` · `score` · `passed` (bool) · `evaluated_at` · `evaluated_by` · timestamps.
⛔ `TenantPolicy::apply`. ⚠ `passed` **hesaplanıp saklanır**, canlı eşikten türetilmez: eşik admin ayarıdır ve sonradan değişirse geçmiş sonuç değişmemeli.

### 1.6 `training_completions`
`id` · `tenant_id` · `training_enrolment_id` · `completed_at` · `signed_register_document_id` (FK) · `kums_evidence` · `declaration_conflict_resolution` (nullable metin) · `resolved_by` · timestamps.
⛔ `TenantPolicy::apply`. Benzersiz: `training_enrolment_id`.
⚠ Dört unsurun **dördü de bu satırda** izlenebilir (spec §8.4).

### 1.7 `certificate_records`
`id` · `tenant_id` · `training_enrolment_id` · `certificate_no` · `issued_on` · `valid_until` · timestamps.
⛔ `TenantPolicy::apply`.
⛔ **Belge yok**: dosya kolonu, storage path, mime — hiçbiri yok ve olmayacak (spec §8.5, çatı §140). Migration yorumu bunu yazar, yoksa biri "eksik" sanıp ekler.

---

## 2. Domain

`app/Domain/Trainings/CompletionGate.php`
```
opens(bool $signedRegister, bool $examSettled, bool $conflictResolved, bool $kumsEvidence): bool
missing(): array   // hangi unsur eksik — ekran bunu yazar
```
⛔ Dört unsur (spec §8.4, çatı §143). ⚠ `missing()` var çünkü *"tamamlanamıyor"* diyen bir ekran işe yaramaz; **hangisinin** eksik olduğu eylemi doğurur.

`app/Domain/Trainings/DeclarationConflict.php`
```
detect(bool $declared, bool $signed): ?string   // 'declared_not_signed' | 'signed_not_declared' | null
```
⛔ Çelişki **çözülmeden kapı açılmaz** (spec §8.4, çatı §190).

`app/Domain/Trainings/ReserveList.php` — sıra önerisi; ⛔ **otomatik terfi yok** (spec §5.3): sınıf sırayı **önerir**, terfi eden action insan kararını ve gerekçesini ister.

---

## 3. Action'lar

| Action | Not |
|---|---|
| `EnrolParticipant` | ⛔ Kontenjan dolu ise `reserve` statüsüyle açar, kontenjanı **işgal etmez** |
| `PromoteFromReserve` | ⛔ **Gerekçe zorunlu**, audit'e yazılır (spec §5.3). ⚠ KUMS bayrağı üretir |
| `WithdrawEnrolment` | slot boşaltır → yedekten çağırma açılır |
| `MarkAttendance` | oturum × kayıt; zaman damgası ve işaretleyen |
| `DeclareAttendance` | katılımcının kendi beyanı (magic link ya da panel) |
| `FileSignedRegister` | imzalı listeyi belge motoruna bağlar (`SCOPE_TRAINING`) |
| `RecordExamResult` | eşiği **eğitim tanımından** okur (kırmızı çizgi 14), `passed`'i saklar |
| `ResolveDeclarationConflict` | ⛔ gerekçe zorunlu; çözülmeden `CompleteTraining` reddeder |
| `CompleteTraining` | `CompletionGate` dört unsuru sorar; eksikse `missing()` ile reddeder |
| `RecordCertificate` | numara + veriliş + geçerlilik. ⛔ Dosya **almaz** |

⚠ **KUMS bayrağı** (spec §8.3): bildirilmiş bir eğitimin kayıt listesi değiştiğinde `trainings` üstünde `kums_update_required` işaretlenir; `StartTraining` bu bayrak açıkken **reddeder**. ⛔ Bayrağı üreten yer tek olmalı — `EnrolParticipant`/`PromoteFromReserve`/`WithdrawEnrolment` ortak bir `FlagKumsUpdate` çağırır.

⛔ **KVKK** (spec §8.6): `EnrolParticipant`, kişinin geçerli rızası yoksa **reddeder**; rıza `consents` ✓ üstünde. Rıza akışı katılımcı onboarding'inde, ekranı sonraki turda — bu chunk **kapıyı** kurar.

---

## 4. Query'ler

| Query | Kullanan |
|---|---|
| `Customer\MyTrainings` | `15` liste + detay (katılımcılar, yoklama sonucu, süreç şeridi) |
| `Customer\MyCertificates` | `16` sertifikalar — kod + tarihler; ⛔ her satır **disclaimer** taşır (spec §8.5) |
| `Customer\ComplianceSummary` | `11`'in uyum kartları — chunk 1'in `null` sözleşmesini **doldurur** |
| `Office\TrainingRoster` | eğitimin kayıt listesi; `41` ve `22` bunu tüketecek |

⚠ `ComplianceSummary` chunk 1'in `PortalDashboard`'ına bağlanır — bu chunk'ın **caller'ı olan** parçası budur (P-7).

---

## 5. Frontend

| Dosya | Tasarım |
|---|---|
| `pages/Portal/Trainings.vue` + `TrainingShow.vue` | `15` |
| `pages/Portal/Certificates.vue` | `16` sertifikalar |
| `molecules/CertificateRow.vue` | kod + tarih + **zorunlu disclaimer** |

⛔ `CertificateRow` disclaimer'ı **prop olarak almaz** — bileşenin kendisi yazar. Prop olsaydı bir sayfa unutur ve orada kural sessizce düşerdi (`PhoneInput` dersi, kırmızı çizgi 17).

---

## 6. Test planı

| Grup | Senaryo |
|---|---|
| `omurga` (unit) | `CompletionGate` dört unsurun her biri eksikken kapanır ve `missing()` doğru söyler · `DeclarationConflict` iki yön · `ReserveList` sıra önerir, terfi etmez |
| `katilimci` | kayıt · kontenjan dolunca `reserve` ve kontenjanı işgal etmemesi · terfi gerekçesiz reddedilir · `withdrawn` slot açar, `no_show` **açmaz** · yoklama zaman damgası · beyan ikincil kalır |
| `katilimci` | ⛔ **KUMS:** bildirilmiş eğitimde kayıt değişince bayrak doğar; bayrak açıkken `StartTraining` reddeder |
| `katilimci` | ⛔ **KVKK:** rızasız kayıt reddedilir |
| `katilimci` | tamamlama: dört unsur tamam → açılır; biri eksik → reddeder ve hangisi olduğunu söyler |
| `mevzuat` | sınav eşiği **eğitim tanımından** okunur; kodda sabit yok · `passed` saklanır, eşik sonradan değişince geçmiş sonuç değişmez |
| `musteri` | `15` detayda yoklama sonucu görünür · `16` her sertifika satırında disclaimer var |
| `omurga-rls` | yedi yeni tenant tablosu için negatif test |
| `musteri` | ⛔ **`PanelAccess` geçişi:** yalnız `training_enrolments`'ta kaydı olan kişi katılımcı yeteneği alır; eski tabloda kaydı olan **almaz** (taşıma sonrası ikisi de aynı olmalı) |
| `omurga` | ⛔ **audit** (AD-06): kayıt, terfi (gerekçesiyle), tamamlama ve sertifika kaydı `audit_writes` üretir; yazım aynı transaction'da |

---

## 7. Kapsama

| Spec | Karşılığı |
|---|---|
| M-23 | `session_attendances` + `FileSignedRegister` + testler |
| M-24 | `FlagKumsUpdate` + `StartTraining` reddi |
| M-25 | `RecordExamResult` eşiği tanımdan okur (`mevzuat` grubu) |
| M-26 | `certificate_records` dosya kolonsuz + `CertificateRow` disclaimer'ı |
| M-27 | `EnrolParticipant` rıza kapısı |
| M-40 | `CompletionGate` dört unsuru sorar; `CompleteTraining` eksikse reddeder |
| M-41 | `DeclarationConflict` + `ResolveDeclarationConflict` (gerekçe zorunlu); çözülmeden kapı açılmaz |
| M-28 | `ComplianceSummary` kartları doldurur |
| M-38 | `withdrawn` vs `no_show` |

⚠ M-33'ün (yedek liste) yarısı burada (`reserve` statüsü, terfi kararı), yarısı chunk 4'te (teklif metnindeki *"yedek liste"* ibaresi).
