# Şablondan eğitim belgesi — implementasyon planı

*v1.2 · 20.10.2026 · EGT-225 · heyet 834 (6 bulgu) ve 837 (2 bulgu) sonrası · Tasarım: `design/52 Egitim Belgesi.dc.html` · Brief: `docs/design/katilim-belgesi-brief.md` · Kararlar: `proje-haritasi.md` «EGT-218 kararları» madde 5, «EGT-225 belge içeriği», «EGT-225 tasarım 52 kararları» (Sets, 20.10.2026) · Önceki: EGT-218 (kategori, marka, belge modu ve düzenleyen — canlıda)*

**Hedef:** Belgeyi düzenleyen **3E ya da marka** olan eğitimlerde, eğitim tanımına yüklenen **şablonun** üzerine sistemin her katılımcının belgesini basması; belge numarası, QR, doğrulama sayfası, düzeltme/iptal.

## Doğrulanmış mevcut durum (20.10.2026, koddan)

| Konu | Bugün | Kanıt |
|---|---|---|
| Belge modu | `certificate_mode` ∈ `none` / `issued_elsewhere` (CHECK); düzenleyen türü + adı | `2026_11_04_000100_…`, `TrainingDefinition` |
| Belge kaydı | `certificate_records` (kiracılı, RLS): numara, tarih, geçerlilik, `issuer`; dosya yok, sistem belge üretmez | `CertificateRecord`, `RecordCertificate` |
| Kaydı oluşturan | Ofis elle girer (`RecordCertificate`): tamamlanmış + (sınav varsa) geçmiş katılımcı, kişi başı bir kayıt | `RecordCertificate.php` |
| Şablon/antet altyapısı | `document_templates` (tür, sürüm, `is_current`, boyut, `issuing_company_id`), yükleme eylemi boyut okur, `HouseFile::stream` ile sunulur | `DocumentTemplate`, `UploadDocumentTemplate`, `HouseFile` |
| Numara serisi | Fatura: `invoice_number_sequences` (şirket × yıl, `last_number`), `InvoiceNumber::format` → `ÖNEK + YIL + 9 hane` | `2026_11_01_000400_…`, `InvoiceNumbers` |
| QR | `bacon/bacon-qr-code` kurulu (iki adımlı doğrulama) | `composer.json` |
| PDF → görsel | Yerelde `pdftoppm` var; **canlı sunucuda yok** (yalnız PHP GD) | `which pdftoppm` (dev ✓, prod ✗) |
| Eğitimi veren 3E şirketi | `trainings.cost_branch_id` → şubenin şirketi; yoksa varsayılan şirket | `trainings` kolonları, `IssuingCompanies::forBranch` |
| Yazdırma yolu | Fatura belgesi: Inertia sayfası, `@page` A4, tarayıcı yazdırma | `organisms/InvoiceDocument.vue`, `invoice.css` |

## Kararlar (teknik)

| # | Karar | Gerekçe |
|---|---|---|
| B-1 | `certificate_mode` CHECK'ine **`generated`** eklenir. Yalnız düzenleyen türü `institution` (3E) ya da `brand` iken seçilebilir (CHECK + form). | Brief: «düzenleyen 3E ya da marka ise üretir»; mevzuatın sahibi ve başka kuruluş kendi belgesini verir. |
| B-2 | **Belge seri öneki:** `brands.certificate_series_prefix`, `issuing_companies.certificate_series_prefix` — **tam 3 karakter, büyük harf ya da rakam** (`^[A-Z0-9]{3}$`, fatura önekinin kuralı: `InvoiceNumber::PREFIX`; Sets'in örnekleri WNX ve 3EA uyar — heyet 834 F-1), CHECK, iki tabloda da tekil. Sayaç `certificate_number_sequences` (`issuer_type` `brand`/`company`, `issuer_id`, `year`, `last_number`; PK üçlü). Format `InvoiceNumber::format` ile aynı: önek + yıl + 9 hane. Numara **satır kilidiyle, üretimle aynı işlemde** alınır; boşluksuz. | Sets kararı 1: ayrı alan, fatura serisinden bağımsız. 3E belgesinde düzenleyen = eğitimi veren 3E şirketi (B-9). |
| B-3 | **Şablon:** `document_templates`'e yeni tür `certificate_template` + `training_definition_id` (nullable FK). Sürümlü (`is_current`, eski sürüm silinmez); belge, üretildiği sürüme bağlanır (`certificate_records.template_id`). ⚠ **Mevcut iki tekil indeks** (`document_templates_version_per_kind`: `kind, COALESCE(issuing_company_id,0), version`; `document_templates_one_current`: aynı anahtar `WHERE is_current`) tanım başına sürümü kaldırmaz: ikisi `WHERE kind <> 'certificate_template'` ile daraltılır, şablon için iki yeni kısmi tekil indeks: `(training_definition_id, version) WHERE kind = 'certificate_template'` ve `(training_definition_id) WHERE kind = 'certificate_template' AND is_current`. CHECK: `certificate_template` ⇔ `training_definition_id IS NOT NULL` (heyet 834 F-3). Tanımda `certificate_imprint_position` (`bottom` varsayılan / `top`) — tasarımın «künye konumu: üst» tek ayarı. | Tasarım 52 Ekran 1: «yükleme yeni sürüm açar · eski belgeler eski şablonla kalır». |
| B-4 | **Şablon kuralı** (yükleme eylemi): PNG ya da tek sayfalık PDF; A4 oranı (1 : 1,414, ±%2), en az 3508 × 2480 px (yatay) / 2480 × 3508 (dikey). PDF `pdftoppm -r 300 -singlefile` ile PNG'ye çevrilir, saklanan PNG'dir; çok sayfalı/bozuk PDF reddedilir. Hata metinleri tasarımın üç kartı. **Canlı sunucuya `poppler-utils` kurulur** — deploy ön koşulu, `docs/infra/ortamlar.md`'ye yazılır; araç yoksa yükleme açık bir hata verir (sessiz düşmez). | Sets kararı 3. |
| B-5 | **Üretim zamanı:** tek bir eylem (`GenerateCertificate`), üç tetikleyici: (a) katılımcının **tamamlama kaydı yazıldığında** (`CompleteTraining`), (b) **tamamlanmış bir katılımcı için sonradan geçen sınav sonucu girildiğinde** (`RecordExamResult` — yeniden sınav; `CompleteTraining` sonucun var olmasını ister, geçmesini değil — heyet 834 F-4), (c) **şablon yüklendiğinde** bekleyenler için. Koşul hep aynı: tamamlanmış + (sınav varsa) son deneme geçti + geçerli belgesi yok + şablon var. Numara o anda alınır. Elle giriş yolu (`RecordCertificate`) `generated` tanımda kapalıdır. | Tasarım 52 3a dipnotu; «sınav notu girilince üretilir». |
| B-6 | **Kayıt:** ⚠ mevcut `certificate_records_one_per_enrolment` (`training_enrolment_id` tekil `WHERE training_enrolment_id IS NOT NULL`) düzeltmedeki ikinci satırı reddeder: indeks **`WHERE training_enrolment_id IS NOT NULL AND status = 'valid'`** olarak yeniden kurulur — kişi başı bir **geçerli** belge (heyet 834 F-2). `certificate_records` ek kolonlar: `generated` (bool), `template_id`, `status` (`valid`/`corrected`/`cancelled`), `replaced_by_id`, `voided_at`, `void_reason`, `voided_by`. Üretilen belgenin içeriği (ad, kimlik no, eğitim, tarih, süre, yer, marka, düzenleyen, veren) **kopya** olarak `printed` (şifreli `encrypted:array` — kimlik no taşır) kolonunda; belge her açılışta bu kopyadan basılır. | Fatura belgesi ilkesi: kesildiği gibi basılır. |
| B-7 | **Doğrulama kaydı:** kiracısız `certificate_verifications` tablosu (numara tekil, ad soyad, eğitim, tarih·süre, marka, düzenleyen, düzenleme tarihi, geçerlilik, durum, düzeltildiyse yeni numara) — **kimlik no yok**. Üretim, düzeltme ve iptalle aynı işlemde yazılır/güncellenir. Muafiyet listesine gerekçesiyle eklenir. | Sets kararı 2: her firmanın panel kullanıcısı doğrulayabilir; `certificate_records` RLS ile başka kiracıya kapalı. Kimlik no kopyası bu tabloya girmez (AD-16). |
| B-8 | **Doğrulama sayfası:** panelde `GET /dogrula/{numara}` ve numara sorgu formu (`/dogrula`); **giriş zorunlu** (müşteri oturumu; güçlü/zayıf fark etmez — kimlik no göstermez). Ofis kullanıcıları için ofiste aynı sayfa (`/dogrula/{numara}`). Hâller: geçerli · geçersiz (düzeltildi → güncel numara bağlantısı; iptal → tarih, gerekçe yok) · bulunamadı. Her sorgu erişim kaydına (`recordQuery`, sahibinin kiracısı) yazılır. QR, panel adresini taşır. İletişim e-postası tasarımdaki sabit adres değil, iş ayarından (`settings`) okunur. | Sets kararı 2; kırmızı çizgi 8/9. |
| B-9 | **Belgede yazanlar** (tasarım 52 Ekran 2): «SAYIN», ad soyad, T.C. kimlik no (açık), eğitim adı, tarih · süre (saat) · yer; künye üçlüsü: İçerik (marka) · Belgeyi düzenleyen · Eğitimi veren (eğitimin şubesinin 3E şirketi, yoksa varsayılan). Kurallar: marka yoksa «İçerik» düşer; düzenleyen = veren ise tek hücre «Düzenleyen ve veren: …»; ad 32 karakteri aşarsa 32px, 44'ü aşarsa 26px, tek satır; eğitim adı en çok iki satır. Künye şeridi: belge no, düzenleme, geçerlilik (süresizse satır yok); QR + doğrulama adresi. | Sets: kimlik no açık. |
| B-10 | **Görünüm:** ortak organizma (`organisms/TrainingCertificate.vue`) + ince ofis/portal sayfaları; şablon görseli sayfa arka planı (yetkili rota ile sunulur, `HouseFile`), basım bloğu üstünde; A4 yatay/dikey şablonun oranından; `@page`; 375px'te yatay kayar. İptal/düzeltme: taramalı üst kutu + «GEÇERSİZ» saydam damga + künyede «GEÇERSİZ» (fatura dili). | Tasarım 52 Ekran 2 ve iptal kartı. |
| B-11 | **Açma** (brief §2.3'ün üç yüzeyi): **katılımcının kendi ekranı** (`Portal/Participant/Panel` belge listesi — heyet 834 F-5), ofis eğitim sayfası katılımcı tablosu: Belge sütunu (numara + tarih / «düzeltildi · eski …» / «belge yok — …» / «… girilince üretilir») ve «Belgeyi aç / PDF»; başlıkta «Tümünü PDF olarak aç (n)» (tek sayfada art arda belgeler, sayfa sonlarıyla). Portal: personel sertifikaları satırında düğme; şablon yoksa «Belge hazırlanıyor», tamamlamadıysa «Belge düzenlenmedi». Her açılış erişim kaydı (kimlik no açık; sahibinin kiracısı). | Tasarım 52 Ekran 3a/3b. |
| B-12 | **Şablon yok şeridi** (ofis eğitim sayfası, amber): üretilemeyen kişi sayısı + «Eğitim tanımına git» (backoffice bağlantısı). Backoffice formunda da uyarı (form kaydedilir). | Tasarım 52 «Şablon yok» kartları. |
| B-12b | **Üretimin denetim kaydı:** her üretim `AuditWriter` ile aynı işlemde yazılır (olay `training.certificate_generated`: numara, düzenleyen, şablon sürümü, tetikleyici) — bugünkü elle giriş de yazar (`RecordCertificate`; kırmızı çizgi 13, AD-06; heyet 834 F-6). |
| B-13 | **Düzeltme/iptal:** yeni ofis izni `certificate.void` (etiket: belgeyi düzeltme/iptal). Düzeltme: gerekçe + düzeltilecek alan (ad soyad yazımı vb. — kopyadaki alanlar); **yeni numara** ile yeni kayıt, eskisi `corrected`, `replaced_by_id`. İptal: gerekçe, `cancelled`. İkisi de silmez (AD-11), denetim kaydı aynı işlemde (AD-06). | Sets kararı 4. |
| B-14 | **Backoffice formu** (tasarım 52 Ekran 1): «Belge» bölümünde üç seçenekli kart seçici (Belge yok / Dışarıda hazırlanır / Şablondan üretilir), düzenleyen + «Seri öneki · son no» satırı, Geçerlilik (`renewal_months`; «Süresiz» = boş), şablon yükleme/önizleme/sürüm etiketi, künye konumu. «Örnekle önizle» örnek verilerle belgeyi açar. | Tasarım 52 Ekran 1. |

## Veri modeli

- `brands.certificate_series_prefix`, `issuing_companies.certificate_series_prefix` (nullable, CHECK `^[A-Z0-9]{3}$` — B-2, tekil — iki tablo arasında da çakışmaz: `certificate_number_sequences` anahtarı türü de taşır; önek çakışması form doğrulamasıyla engellenir).
- `certificate_number_sequences` (yeni, kiracısız).
- `document_templates.training_definition_id` (nullable FK, indeks) + `kind` CHECK genişler.
- `training_definitions.certificate_imprint_position` (`bottom`/`top`, varsayılan `bottom`); `certificate_mode` CHECK `generated` ile genişler; B-1 CHECK.
- `certificate_records` ek kolonlar (B-6) + `printed` (şifreli); `certificate_records_one_per_enrolment` yalnız geçerli belgeyi kapsar (B-6).
- `document_templates` indeks ve CHECK değişiklikleri (B-3).
- `certificate_verifications` (yeni, kiracısız; numara tekil).
- İzin: `certificate.void` (ofis).

## Görevler

1. Migration'lar, modeller, muafiyet ve izin kayıtları (+ yetki matrisi, etiketler).
2. Seri ve numara (`CertificateNumbers`), şablon yükleme (PNG/PDF, kurallar, sürüm), backoffice formu (B-14) ve marka/şirket formlarına önek alanı.
3. Üretim (`GenerateCertificate`; B-5'in üç tetikleyicisinden çağrılır: `CompleteTraining`, tamamlanmış katılımcı için `RecordExamResult`, şablon yüklemesi), doğrulama kaydı, düzeltme/iptal eylemleri.
4. Belge görünümü (organizma, sayfalar, şablon görsel rotası, QR), ofis tablo/şerit, portal satırı, toplu açma.
5. Doğrulama sayfası (panel + ofis), sorgu formu, erişim kaydı.
6. Altyapı: canlıya `poppler-utils`, `ortamlar.md`; yardım (ofis eğitim sayfası, portal sertifikalar, backoffice yok), changelog, harita.

## Testler

- Önek: 3 karakter (harf/rakam) kabul, 2 ya da 4 karakter reddedilir; marka ile şirket aynı öneki alamaz.
- Şablon indeksleri: iki farklı tanımın şablonu aynı sürüm numarasını taşıyabilir; bir tanımın tek güncel şablonu olur; antetlerin mevcut kuralı bozulmaz.
- Numara: düzenleyen başına sıralı, boşluksuz, yıl değişince 1'den; iki eşzamanlı üretim aynı numarayı almaz (kilit).
- Şablon: PNG kabul; düşük çözünürlük, A4 dışı oran, çok sayfalı PDF reddedilir (tasarımın üç metni); tek sayfalık PDF PNG'ye çevrilir (pdftoppm yoksa açık hata); yeni sürüm eskisinin belgelerini değiştirmez.
- Üretim: tamamlanınca üretilir; sınavı geçmeyen için üretilmez; tamamlandıktan sonra yeniden sınavı geçince üretilir; şablon yoksa bekler, yüklenince üretilir; her üretim denetim kaydı yazar; `generated` tanımda elle giriş kapalı; `generated` yalnız 3E/marka düzenleyende seçilebilir.
- Belge içeriği: kopyadan basılır (kişinin adı sonradan değişse belge değişmez); marka yoksa «İçerik» yok; düzenleyen = veren tek hücre.
- Erişim: ofiste yetkili açar; portalda yalnız kendi firmasının personeli; katılımcı yalnız kendi belgesini; her açılış erişim kaydı.
- Düzeltme sonrası kişinin iki belge satırı olur (biri geçersiz), ikinci geçerli belge reddedilir.
- Düzeltme/iptal: izinsiz reddedilir; düzeltmede yeni numara, eski geçersiz, doğrulama kaydı güncellenir; iptal gerekçesiz reddedilir.
- Doğrulama: girişsiz yönlendirir; başka firmanın kullanıcısı geçerli belgeyi görür, kimlik no görmez; geçersiz ve bulunamadı hâlleri; sorgu erişim kaydı.
- Tarayıcı: belge (ofis + portal) ve doğrulama sayfası açılır, konsol hatası yok, 375'te belge yatay kayar, doğrulama tek kolon.

## Rollout

Ön koşul: canlıya `poppler-utils`. Mevcut tanımlar etkilenmez (`generated` yalnız seçilince). Yönetici marka/şirket belge öneklerini ve şablonu girer.

## Rollback

`git revert` + migration `down`: yeni tablolar ve kolonlar düşer; üretilmiş belge varsa `down` durur ve sayısını yazar (AD-11 — üretilmiş belge silinmez; karar Sets'in).

## Kapsam dışı

Herkese açık (girişsiz) doğrulama ve `dogrula.3egrp.com` alt alanı (Sets kararı 2); şablonda serbest yerleşim (yalnız künye üst/alt); belgenin e-postayla gönderimi.
