# Review — Fatura belgesi implementasyon planı (v1.0) — 2026-10-20

> **Review type:** plan-review
> **Previous review:** N/A (ilk review)
> **Upstream document(s) read:** `design/39 Fatura Belgesi.dc.html` (tamamı), `docs/design/fatura-belgesi-brief.md`, `proje-haritasi.md` §4.4 «Şubeler» → «Fatura belgesi (tasarım 39) için kararlar» (satır 910), `docs/plans/sube-plan.md` (A5 / Rollback kalibrasyonu)
> **Other cross-referenced documents:** `CLAUDE.md`, `app/Domain/Sales/QuoteTotals.php`, `app/Actions/Sales/IssueInvoice.php`, `app/Actions/Sales/AddQuoteItem.php`, `app/Models/Customer.php`, `app/Models/Invoice.php`, `app/Support/Invoicing/IssuingCompanies.php`, `resources/js/pages/Office/Quotes/Printout.vue`, `routes/office.php`, `routes/panel.php`, `app/Support/Audit/AccessLogger.php` (varlık), `app/Policies/InvoicePolicy.php` (varlık)
> **Stage locks in effect:** Sets kararları (20.10.2026) kilitli — spec yok (Sets kararı); EGT-219 (sube-plan A5) canlıda ve kilitli

---

## Upstream Coverage Matrix

### Sets kararları (proje-haritasi.md:910)

| Upstream madde | Durum | Kanıt |
|---|---|---|
| (1) Fatura bilgileri lokasyondan değil, müşteri formunda «Fatura bilgileri» bölümü | ✓ | plan F-1/F-2 (satır 30–31); `customers`'ta adres kolonu olmadığı doğrulandı (`Customer.php` — yalnız `tax_office`, `tax_number`, `national_id`) |
| (2) Kalemin ikinci satırı, kesimde kopyalanır | ✓ | plan F-9 (satır 38): `invoice_items.detail`, `IssueInvoice` içinde kopya |
| (3) KDV tanım/paket başına backoffice'te, boşsa genel; teklif kalemi oranı alır; faturaya kalem bazında; toplamlarda oran başına satır | ⚠ | plan F-4…F-8 fatura tarafını tam karşılıyor; **F-7 bunu teklif çıktısına (37) ve portal teklifine de genişletiyor** — karar metni fatura belgesi bağlamında, 37 için kayıtlı Sets onayı yok → **F-R4** |
| (4) İndirim toplamlarda tek satır, teklif çıktısındaki (37) gibi | ✓ | plan F-10 (satır 39); 37'nin mevcut davranışıyla birebir (`Printout.vue:59-70`: liste − net, indirim yoksa satır yok) |
| (5) Yasal ibare şirket kaydından, boş olabilir | ✓ | `issuer_snapshot.legal_notice` zaten kopyada (`IssuingCompanies.php:67`); plan kapsam dışı bölümü metni Sets'te bırakıyor |
| Şahıs T.C. kimlik no belgede açık | ✓ | plan F-12 (satır 41); ekranda maskeli kalma notu var |

### Brief + tasarım 39 öğeleri

| Öğe | Durum | Kanıt |
|---|---|---|
| B1 antet + kesen şirket künyesi, kesim anındaki kopyadan | ✓ | plan F-11; `issuer_snapshot` unvan/vergi/adres/telefon/antet id taşıyor (`IssueInvoice.php:144`, `IssuingCompanies.php:63-75`) |
| B1 varyantı — **antet yüklenmemişken sade başlık** (alan boş bırakılmaz, logo uydurulmaz) | ❌ | planda karşılığı yok; F-11 yalnız antet görselinin route'unu tarif ediyor (tasarım 39:221-228) → **F-R2** |
| B2 no kutusu: fatura no · tarih · son ödeme · referans kodu | ✓ | mevcut kolonlar: `invoice_number`, `issued_at`, `due_on` (`IssueInvoice.php:143-151`), referans kodu (`:221-227`) |
| B2 varyantları: yerine kesildi satırı · numarasız eski kayıt | ✓ | plan F-13; `replaces_invoice_id` mevcut (`Invoice.php:25,223-226`), eski faturalarda `invoice_number` null |
| B3 firma / şahıs alıcı bloğu, kesim anı kopyası | ✓ | plan F-3 `recipient_snapshot` (şifreli); `customers.legal_type` mevcut (`Customer.php:31`) |
| B4 kalem tablosu: KDV sütunu + iki satırlı açıklama + thead tekrarı | ✓ | plan F-8, F-9, F-14; `invoice_items.vat_rate` bugün de var (`IssueInvoice.php:211`) |
| B4 çok sayfa: devam başlığı, satır bölünmez, sayfa n/m | ✓ | plan F-14 — riski işaretli, desteklenmeyen tarayıcı için bilinçli fallback tanımlı |
| B5 oran başına KDV satırı + matrah | ✓ | plan F-6 `vat_by_rate` (oran → matrah, KDV) |
| B6 not + ödeme bilgileri (banka, 2–3 hesap) | ✓ | `invoices.note` mevcut (`Invoice.php:60`); `issuer_snapshot.bank_accounts` (`IssuingCompanies.php:69-73`) |
| B7 ayak: yasal ibare (boşsa satır yok) · kısa künye · sayfa no | ✓ | plan F-13/F-14 |
| Hâl 2 iptal: kutu (tarih · gerekçe · yerine kesilen) + saydam damga + ayakta İPTAL, tam içerik | ✓ | plan F-13; `cancelled_at`/`cancelled_reason` mevcut (`Invoice.php:27,74`) |
| Yalnız kesildi/iptal yazdırılır; taslak pasif düğme | ✓ | plan F-11 (`issued`/`cancelled`), F-15 |
| Ekranda açılış: ofis + portal, yeni sekme, tarayıcı yazdırma | ⚠ | ofis rotası mevcut kalıpla uyumlu (`routes/office.php:711`); **portal rotasındaki `{token}` tanımsız** → **F-R1** |
| 375px yatay kaydırma; siyah-beyaz okunurluk | ✓ | tarayıcı spec'i (plan satır 80); iptal işareti renksiz de anlaşılır (F-13 kutu) |

### Özellikle bakılması istenenler

| Konu | Durum | Kanıt |
|---|---|---|
| `QuoteTotals` geriye dönük uyum (tek oranlı teklif aynı sonuç) | ✓ | plan F-6 + test «tek oran = bugünkü sonuç, mevcut testler + eşitlik testi» (satır 75); mevcut `of()` imzası satır dizisine isteğe bağlı `vat_rate` eklemeye uygun (`QuoteTotals.php:49`) |
| Teklif indiriminin oran gruplarına dağıtımı + yuvarlama | ✓ | plan F-6 (net oranında dağıtım, oran grubu başına KDV) + «yuvarlama toplamı tutar» testi (satır 75) |
| Dokuz çağıranın eksiksizliği | ✓ | grep ile doğrulandı: plandaki 9 isim + `AddQuoteItem` (F-5'te, `of()`'u yalnız tavan için `vatRate: 0.0` ile çağırır — `AddQuoteItem.php:151`, isteğe bağlı satır oranıyla geriye uyumlu) + `IssueInvoice` (F-8'de) + `TrainingDetail` (yalnız kurucu, `TrainingRevenue` üzerinden listede — `TrainingDetail.php:143`) |
| Alıcı kopyasının şifreli saklanması (F-3) | ✓ | `encrypted:array` cast + «ham kolonda T.C. no görünmez» testi (satır 77); `customers.national_id`'nin mevcut şifreli kalıbıyla tutarlı |
| Belgede erişim logu (F-12) | ✓ | `AccessLogger` mevcut; şahıs alıcıda ofis + portal her açılış, firma alıcıda yok; testleri yazılı (satır 79) |
| Çok sayfa teknik riski (F-14) | ✓ | «ilk doğrulanacak teknik nokta» olarak işaretli, fallback davranışı bilinçli tanımlı |

---

## ✅ What works

- **«Doğrulanmış mevcut durum» tablosu koddan çıkarılmış ve doğru** — spot kontrollerin hepsi tuttu: `customers`'ta adres yok, KDV tek genel oran + `quotes.vat_rate` kopyası, `invoice_items.vat_rate` hep tek oran, `issuer_snapshot` banka/ibare/antet taşıyor.
- **Finansal omurga sağlam:** tek hesap yeri (`QuoteTotals`) korunuyor; teklif indiriminin oran gruplarına net oranında dağıtımı, oran başına matrah+KDV dönüşü ve «tek oranlı teklifte bugünküyle aynı sonuç» eşitlik testi — geriye dönük uyum test zorunluluğuyla kilitlenmiş. Kesimde donan oran (M-35 gerekçesi) teklif kalemine de uygulanmış (F-5).
- **Kesildiği gibi basılır ilkesi iki yönde tamamlanmış:** kesen şirket (mevcut `issuer_snapshot`) + alıcı (`recipient_snapshot`, şifreli, eski faturalarda bilinçli null davranışı).
- **AD-08 uyumu:** KDV oranı tanım/paket başına admin-tanımlı, boşsa genel ayar — koda gömülü oran yok. Data-loss yok, yalnız ekleme; yeni tenant-scoped tablo yok, kolonlar mevcut RLS politikalarına giriyor.
- **Hassas veri ve denetim doğru yerde:** TCKN belgede açık (Sets Q-3) ama her açılış erişim loguna; firma alıcıda log yok; ekranlarda maske sürüyor. Kırmızı çizgi 13'le uyumlu.
- **Test planı üç ayaklı ve tenant'lı:** yetkisiz/kapsam dışı 404, portalda başkasının faturası 404, taslak 404, hâl varyantları, erişim log varlığı/yokluğu, 375px.
- Çok sayfa riski saklanmamış, adıyla işaretlenmiş ve fallback'i bilinçli.

## ⚠️ Findings & Risks (in-scope for this review type)

**F-R1 — Portal belge rotasının `{token}` parametresinin kaynağı tanımsız.** Plan F-11 portal rotasını `GET /faturalar/{token}/belge` yazıyor; faturalarda token diye bir kolon/kavram yok, plan da eklemiyor (veri modeli tablosunda yok). Portalın mevcut kalıbı model binding'dir (`routes/panel.php:398` — `/teklifler/{quote}`); portal fatura listesi bugün yalnız index (`routes/panel.php:501`). Developer bu rotayı, var olmayan bir değeri uydurmadan yazamaz — component/route breakdown bu noktada belirsiz.

**F-R2 — Tasarım 39'un «antet yüklenmemişken» B1 varyantının planda karşılığı yok.** Tasarım (39:221-228) antetsiz hâli ayrı çiziyor: alan boş bırakılmaz, logo uydurulmaz, unvan sola kalın + künye sağa. Plan F-11 yalnız antet görselinin kopyadaki şablon id'sinden basılmasını tarif ediyor; `letterheadOf` null dönebilir (`IssueInvoice.php:343-352`) ve o hâlde belgenin başlığının ne olacağı planda yazılı değil. (Teklif çıktısında eşdeğer fallback var — `Printout.vue:19-26,86` — ama «teklif çıktısı gibi» ifadesi yazdırma yolunu tarif ediyor, başlık varyantını değil.)

**F-R3 — Rollback planı yok.** Planda yalnız «Rollout» bölümü var; migration geri alma / git revert / feature flag ele alınmamış. Migration'lar yalnız ekleme olsa da bu projenin plan kalıbında bölüm olarak mevcut (`docs/plans/sube-plan.md:231`) ve plan review scope'u bunu açıkça ister.

**F-R4 — F-7, kilitli kararın kapsamını teklif çıktısına (37) ve portal teklif ekranına genişletiyor; bu sapmanın kayıtlı Sets onayı yok.** Karar metni (proje-haritasi.md:910, madde 3) «teklif kalemi oranı oradan alır, **faturaya** kalem bazında geçer, toplamlarda oran başına KDV satırı» der — bağlam fatura belgesidir. Plan F-7 ise tasarım 37'nin ve portal teklif ekranlarının (tasarım 13/14) toplamlar kompozisyonunu çok oranlı teklifte değiştiriyor (tek KDV satırı → oran başına satırlar) ve gönderilmiş teklifin dondurulmuş belgesine (`QuoteDocument`) de taşıyor. Aritmetik tutarlılık gerekçesi anlaşılır, ama tasarımcı teslimi bağlayıcıdır ve sapma yalnız Sets söylediğinde olur (CLAUDE.md → Tasarım Uygulama Protokolü); «toplamlarda oran başına KDV satırı» ifadesinin teklifi de kapsayıp kapsamadığı net değil — netleşmeden bu kalem plana giremez (CLAUDE.md → Çelişki/Belirsizlik: varsayımla ilerleme, sor).

## ❓ Open Questions (to be answered before next stage)

1. **F-R4'ün cevabı Sets'te:** çok oranlı teklifte teklif çıktısı (37) ve portal teklif toplamları oran başına KDV satırına geçsin mi, yoksa teklif tarafı bugünkü tek «KDV» satırında mı kalsın (tutar toplamı iki hâlde de aynı)?
2. Serbest fatura kaleminin KDV'si **kesim anındaki genel oran** (plan F-8) — Sets kararlarında geçmeyen bir plan kararı; bilinçli mi, onaylanıyor mu?
3. Fatura bilgisi hiç girilmemiş **şahıs** müşteride (TCKN boş) kesim engellenmiyor (F-2); belgenin alıcı bloğu yalnız ad ile mi basılır? (Yasal belge için bilinçli bir eksik mi — Sets'in bilgisi dahilinde olmalı.)

## ⚠️ Upstream Findings

Yok.

## Verdict: ❌ REJECTED

Matrix'te bir ❌ (antetsiz başlık varyantı) ve iki ⚠ (portal `{token}` belirsizliği, F-7 kapsam genişletmesi) var; buna Rollback bölümü eksiği ekleniyor — dört bulgu (F-R1…F-R4). Finansal çekirdek (F-6 geriye dönük uyum, indirim dağıtımı, dokuz çağıran, şifreli alıcı kopyası, erişim logu, çok sayfa riski) sağlam ve koddan doğrulandı; bulgular dar ve kapatılabilir niteliktedir.
