# Review — Mevzuat dışı eğitim, marka ve belge seçenekleri — implementasyon — 2026-10-20

> **Review type:** impl-review
> **Previous review:** `docs/reviews/review-mevzuat-disi-egitim-plan-re-review-2-2026-10-20.md` (plan v1.2 APPROVED, heyet 830)
> **Upstream document(s) read:** `docs/plans/mevzuat-disi-egitim-plan.md` v1.2 (tamamı); `proje-haritasi.md` «EGT-218 kararları» (6 madde)
> **Other cross-referenced documents:** `CLAUDE.md` (17 kırmızı çizgi tek tek tarandı); incelenen kod: commit `b4654932` (53 dosya) — üç migration, `TrainingDefinition`, `Brand`, `BrandPolicy`, `CertificateIssuer`, `IssuingCompanies`, `StartTraining`, `CompleteTraining`, `RecordCertificate`, `FinishTrainingRequest`, `TrainingPolicy`, iki bakanlık komutu, `TrainingDefinitionForm`, `BrandResource`, 8 sorgu sınıfı, `Plan.vue`, `Show.vue`, `config/authorization.php`, `config/tenancy.php`, `PermissionMatrix`, lang tr/en ×4, factory, arch testleri, 3 test dosyası, changelog, 3 yardım makalesi
> **Stage locks in effect:** Spec yok (kararlar + plan); **Plan v1.2 locked** — plandaki kararlar tartışılmaz, ölçüt koda uyum.

---

## Zorunlu üç tarama

1. **Red-line taraması (17 madde):** İhlal yok. Öne çıkanlar: tenant-scoped yeni tablo yok — `brands` bağlamsız katalog verisi olarak `config/tenancy.php:454`'e kayıtlı ve `tests/Arch/TenancyTest.php` 162 muafiyet sayımına `brands` eklenerek güncellenmiş (çizgi 1-2); `RecordCertificate` issuer'ı audit payload'ına ekliyor ve audit aynı transaction'da (çizgi 13); `BrandPolicy::delete` koşulsuz `false` (çizgi 12 / AD-11); mevzuat kuralı koda gömülmemiş — kategori/mod/tür tanım verisi, 3E unvanı `IssuingCompanies::default()` ile veritabanından (çizgi 9/14); değişen dosyalarda `env(` yok (çizgi 7); commit mesajında AI attribution yok (çizgi 11); `brands` migration `timestampsTz()` (timestamptz kuralı).
2. **Wire taraması (feature teslimi):** `BrandResource` backoffice panel keşfine giriyor (`BackofficePanelProvider.php:163` `discoverResources(app_path('Filament/Backoffice/Resources'))`); plan ekranı kartı `RegulationOptions::all()` dönüşüne ekleniyor ve `Plan.vue:321-323` `non_regulatory` bayrağıyla süzüyor; bakanlık komutları zaten schedule'da olan mevcut komutlar (yalnız atlatma eklendi); `CertificateIssuer` `RecordCertificate` constructor'ından çağrılıyor; `labelWithBrand()` 7 üretim okuyucusunda kullanımda.
3. **Dil taraması:** Kod tarafı İngilizce (yeni sınıf/metot/kolon adları, yorumlar); kullanıcı metinleri `lang/tr` + `lang/en` üzerinden ve **dört dosyada anahtar paritesi birebir doğrulandı** (brand, training, trainings, permission). Migration/model docblock'larındaki Türkçe alıntılar `arch-allow: turkish-content` işaretli; `RuleScanTest` güncellenmiş.

## Upstream Coverage Matrix (plan v1.2 → kod)

| Plan maddesi | Durum | Kanıt |
|---|---|---|
| M-1 kategori string + CHECK, mevcut satırlar `regulatory` | ✓ | `2026_11_04_000100:38` default `regulatory`; `:46` `category_known` CHECK; model sabitleri `TrainingDefinition.php:66-68` |
| M-2 iki yönlü CHECK + ek mevzuat yasağı + form görünürlüğü | ✓ | `:47-50` `category_names_regulation` birebir plan metni; ek mevzuat: form `visible(regulatory)` (`TrainingDefinitionForm` diff :151) + model `saved` hook detach (`TrainingDefinition.php:205-209`); mevzuat/bildirim/rehber paneli non-regulatory'de gizli ve `dehydrateStateUsing` ile null; evrak bölümü iki kategoride açık (görünürlük koşulu yok); test `NonRegulatoryTrainingTest.php:89-116` |
| M-3 «Mevzuat dışı» kartı + doğru sayım + options null | ✓ | `RegulationOptions.php` `NON_REGULATORY=0` kart + `nonRegulatoryCount()`, sayımlar `whereNotNull('regulation_id')`; `TrainingDefinitionOptions` `regulation_id` null + `category`; `Plan.vue:321-323` kategori süzgeci; test `:230-244` |
| M-4 durum makinesi + bakanlık görev/hatırlatma yok | ✓ | `StartTraining.php` `readyWithoutMinistry` (Planned + non-regulatory); `OpenMinistryNotificationTasks` + `RemindAboutMinistryDeadlines` `isRegulatory()` atlaması; `TrainingPolicy::notifyMinistry` non-regulatory'yi reddediyor; `Show.vue` şeritte bakanlık adımı kategoriye göre; testler `:146-181` |
| M-4b kategori kilidi (eylem reddeder, form kilitli + sebep) | ✓ | `TrainingDefinition::booted()` `updating` → `LogicException`; `hasTrainings()` tüm eğitimleri sayar (iptal dahil — statü süzgeci yok); form `disabled` + `category_locked` helperText; test `:118-134` |
| M-5 bitirme: KUMS yalnız regulatory | ✓ | `CompleteTraining` `kumsEvidence: !isRegulatory() \|\| dolu`; `FinishTrainingRequest` `nullable` (brief'teki gibi kural action'da); testler `:183-197` + `:172-181` regulatory hâlâ zorunlu |
| M-6 marka: tablo, policy, izinler, matris, görünürlük, faturada yok | ✓ | `brands` migration + `Brand` model (audit, deactivation blocker); `BrandPolicy` (DocumentTypePolicy kalıbı, delete=false); `config/authorization.php:517-520` 4 izin; `PermissionMatrix` 'brand' ailesi; görünürlük: `TrainingDefinitionOptions`, `QuotableTrainings`, `QuoteDocument`, `QuotePrintout`, portal `QuoteController`, `OpenTrainings` — hepsi `labelWithBrand()`; fatura: `BrandOnQuoteTest.php:39` `description === 'Surdurulebilirlik'` (markasız) |
| M-7 certificate_mode + issuer kind/name, 5 CHECK kuralı | ✓ | `:51-59`: mode `none/issued_elsewhere` (generated yok → EGT-225 ✓), kind geçerli küme, `none ⇒ kind NULL`, `other/regulation_owner ⇒ ad dolu` (TRIM'li), `regulation_owner ⇒ regulatory`, **`brand ⇒ brand_id NOT NULL` (heyet 829 F-A)**; form `issuerKinds()` türleri bağlama göre daraltıyor (Filament options=in kuralı form katmanı, CHECK son savunma); test dataset'i 5 geçersiz kombinasyonun 5'ini de DB'ye karşı deniyor `:136-144` |
| M-8 `none`'da kayıt yok + düzenleyen adı kopyalanır | ✓ | `RecordCertificate` `issuesCertificate()===false` → refus; `issuer => CertificateIssuer::nameFor()` (institution → `IssuingCompanies::default()->name`, brand → marka adı, diğer ikisi → girilen ad); kopya ilkesi testle kanıtlı: tanım sonradan değişiyor, kayıt 'WeNexus' kalıyor `:199-228`; `Show.vue` belge sütunu yalnız `issues_certificate` ise |
| M-9 SystemReadiness yalnız regulatory + belge veren | ✓ | `SystemReadiness` `$owing` closure: `category=regulatory` + `certificate_mode<>none`; test `:257-265` üç durumu ayırıyor |
| M-10 üç gruplama listesi «Mevzuat dışı eğitimler» başlığı | ✓ | `CompanyProfile.php:110`, `TrainingRequestController` (Create.vue verisi), `RequirementMap.php:156` — üçü de `?? __('training.categories.non_regulatory_group')`; test `:267-274` |
| M-11 uyarı/bildirim/zincir yalnız regulatory | ✓ | Veride sabitlenmiş: migration `_000200` CHECK (`notification_type/days` non-regulatory'de NULL) — **plana göre fazladan ama planı güçlendiren** bilinçli ekleme, yeni risk getirmiyor (deploy anında non-regulatory satır yok, update 0 satır); form bildirim bölümü gizli+null; rehber paneli gizli; test `:101` |
| Görev 1 (migration+model+backoffice) | ✓ | Yukarıdaki satırlar + `BrandResource` (liste, sayaç, LifecycleActions) + tanım formunda kategori en üstte |
| Görev 2 (durum/bitirme/belge) | ✓ | M-4/M-5/M-8 satırları |
| Görev 3 (kart, gruplar, hazırlık, okuyucular) | ✓ | M-3/M-9/M-10/M-11 satırları |
| Görev 4 (marka görünürlüğü uçtan uca) | ✓ | M-6 satırı — plan ekranı, teklif kalemi, portal teklif isteği, açık eğitimler, QuoteDocument, çıktı |
| Görev 5 — yardım, changelog, **harita** | ⚠ | Yardım: 3 makale güncellendi (`docs/yardim/planlama-bakanlik.json`, `planlama-egitim-planlama.json`, `satis-teklif-hazirlama.json`) ✓; changelog `2026-10-20.json` ✓; **harita güncellemesi yok** → F-1 |
| Görev 6 — tasarım kapsamı (kart/rozet Sets kararıyla) | ✓ | Plan bu ekleri tasarım-dışı olarak önceden onayladı; kart mevcut kart desenini birebir kullanıyor, rozet `labelWithBrand` ek deseni |
| Test listesi (plan §Testler, 13 kalem) | ✓ | `NonRegulatoryTrainingTest` (16), `BrandResourceTest` (3: 403/200/AD-11+blocker), `BrandOnQuoteTest` (1: teklif var/fatura yok); gruplama, kilit, düzenleyen, M-9, M-11, «mevcut testler değişmeden» → tam koşu yeşil |
| Rollout (mevcut satırlar regulatory+issued_elsewhere, davranış aynı) | ✓ | Kolon default'ları; davranış regresyonu CI ile doğrulandı |
| Rollback (`down` mevzuatsız satırda durur ve adlarını yazar) | ✓ | `_000100 down()` orphan kodlarını toplayıp `RuntimeException` ile sayıyor; veri silinmiyor |

## Doğrulama sonuçları (bu oturumda koşuldu)

| Kontrol | Sonuç |
|---|---|
| `pint --test` (commit'in tüm PHP dosyaları) | passed |
| `composer analyse` | passed, 0 hata |
| 3 yeni test dosyası (yerel) | **20/20 passed** (36 assertion) |
| TeamCity full run **#1831** | **SUCCESS — 2764 passed, 0 failed, 0 skipped** (`summary` çıktısıyla doğrulandı) |

## ✅ What works

- Beş CHECK kuralının beşi de migration'da birebir ve **beşi de geçersiz kombinasyon başına ayrı test datasetiyle** DB'ye karşı kanıtlanmış — F-A dahil (`brand ⇒ brand_id`).
- Kopya ilkesi (M-8) testte tam senaryoyla kanıtlı: belge kaydı sonrası tanımın düzenleyeni değiştiriliyor ve kaydın `issuer`'ı değişmiyor; 3E adı hardcode değil, `issuing_companies` tablosundaki varsayılan şirketten.
- Kategori kilidi hem modelde (`updating`, her yazma yolunu keser) hem formda (disabled + sebep metni) — plan M-4b'nin iki katmanı da yerinde; `hasTrainings` iptal edilmişleri de sayıyor (plandaki «dahil» kelimesi karşılanmış).
- Migration `_000200` plana göre fazladan ama doğru yönde: M-11'i yedi okuyucuyu tek tek korumak yerine veride sabitliyor; deploy anında etkileyeceği satır yok ve testi var.
- Marka görünürlük sınırı («katalog + teklif + belge, fatura asla») tek testte iki yönüyle birden assert edilmiş.

## ⚠️ Findings & Risks

**F-1 — Plan görev 5'in «harita» kalemi bu teslimde üretilmemiş.** Görev 5 üç kalem sayar: yardım ✓, changelog ✓, **harita ✗** — `proje-haritasi.md`'deki EGT-218 bloğu hâlâ yalnız karar metnini taşıyor, uygulama durumuna dair hiçbir satır yok (commit `b4654932` dosya listesinde `proje-haritasi.md` bulunmuyor ve HEAD'de sonrası commit yok). Depo deseninde harita durum güncellemeleri teslimin parçası (örn. `0d56dafc docs(harita): EGT-221 delivered to review`); ticket kapanış turuna bilinçli bırakıldıysa bu teyit edilmelidir — taşındı mı, unutuldu mu, cevapsız bırakılamaz (Onay Kuralı 6).

## ❓ Open Questions

1. (F-1) Harita güncellemesi ticket kapanış/teslim turuna mı bırakıldı (EGT-221 deseni), yoksa unutuldu mu?

## Notlar (bulgu değil)

- Yardım makalelerinin ekran görüntüleri yenilenmedi; değişen ekran öğeleri (plan ekranı kartı, bakanlık bölümünün non-regulatory hâli) yalnız mevzuat dışı tanım/eğitim **varken** görünür ve bugün hiç yok — mevcut görüntüler hâlâ doğruyu gösteriyor. İlk mevzuat dışı tanım açıldığında plan ekranı makale görüntüsünün tazelenmesi gerekecek.
- `FinishTrainingRequest` plan metnindeki «kategoriye göre required/nullable» yerine «her zaman nullable + kural `CompleteTraining`'de» olarak uygulanmış; davranış sonucu birebir aynı (regulatory kanıtsız reddedilir, hata aynı alanın altına çizilir) ve teslim brief'i bu mekanizmayı açıkça adlandırıyor — sapma değil.

## Verdict: ❌ REJECTED

Kod, plan v1.2'nin M-1…M-11 kararlarının tamamını dosya+satır kanıtıyla karşılıyor; kırmızı çizgi ihlali yok, Pint ve PHPStan temiz, 20 yeni test yerelde ve 2764 test TeamCity #1831'de yeşil. Ancak matrix'te bir ⚠ satır var: plan görev 5'in «harita» kalemi teslim edilmemiş (F-1). Scope içi bulgu varken orta yol yok; harita satırı yazılıp (ya da kalemin ticket kapanışına taşındığı Sets tarafından teyit edilip) re-review istenmelidir.
