# Rapor Sistemi Analizi ve Mutabakat Rehberi

Bu doküman, posmanager tarafındaki üç finansal/operasyonel raporun veri kaynaklarını,
hesaplama kurallarını ve birbirleriyle nasıl mutabık kaldığını açıklar.

| Rapor                      | Görünüm                                                     | Veri Sağlayıcı                      |
| -------------------------- | ----------------------------------------------------------- | ----------------------------------- |
| Kasa Raporu (Finansal Gün) | `resources/views/reports/financial-day.blade.php`           | `ReportController::financialDay()`  |
| Mali Rapor (KDV Kırılımlı) | `resources/views/reports/mali-report.blade.php`             | `ReportController::maliReport()`    |
| Ürün Bazlı Satış Raporu    | `resources/views/livewire/sales-report-component.blade.php` | `App\Livewire\SalesReportComponent` |

> İlgili doküman: `docs/REPORTS.md` poscloud (uzak POS) tarafındaki `ReportsController`'ı
> anlatır; bu doküman ise posmanager tarafındaki raporları kapsar.

---

## 1. Veri Modeli Temelleri

Tüm raporlar `mysql-remote` bağlantısındaki poscloud tablolarından beslenir:

### 1.1 `financial_days` — Günün finansal özeti (poscloud hesaplar)

Poscloud, her finansal gün kapanışında `FinancialDay::recalculate()` ile bu tabloyu doldurur.
Posmanager bu değerleri **yeniden hesaplamaz, okur**.

- `starting` / `ending`: Finansal günün gerçek zaman sınırları (takvim günüyle örtüşmeyebilir,
  gece yarısını aşabilir). Açık (devam eden) günde `ending = NULL`.
- `day`: Günün takvim etiketi (ay/yıl filtreleri bu alan üzerinden yapılır).
- Tahsilat kanalları: `cash_sales`, `credit_card_sales`, `online_sales`, `ticket_sales`,
  `sabee_sales`, `getir_sales`, `yemeksepeti_sales`.
- Kesinti/memo kalemleri: `discount` (indirim), `treat` (ikram), `cancel` (iptal),
  `credit_sales` (vadeli satış), `credit_checkouts` (vadeli tahsilat).

### 1.2 `sales` — Adisyon

- `financial_day_id`: Adisyonun ait olduğu finansal gün. **Raporlarda zaman filtresinin
  kanonik kaynağı budur** (bkz. §4.2).
- `discount`, `discount_pct`, `treat`, `cancel`: İndirim ve ikram **adisyon seviyesinde**
  tutulur; sipariş satırlarına yansımaz.
- `status = 'cancelled'`: Adisyonun **komple iptal** edildiğini gösterir.

### 1.3 `orders` — Sipariş satırı

- `price`, `vat`, `total_price`: Satış anındaki **liste (brüt) fiyat** değerleridir.
  İndirim/ikram bu alanlardan düşülmez:
    - İkram edilen ürün → `total_price` tam fiyat, `payment_type = 10 (İKRAM)`.
    - İndirimli adisyondaki ürün → `total_price` tam fiyat; indirim `sales.discount`'ta.
    - (E-fatura tarafı da aynı varsayımla çalışır: `MysoftEInvoiceService` satırları brüt
      işleyip indirimi allowance olarak ayrıca dağıtır.)
- `canceller_id`: **Sipariş seviyesi (kısmi) iptal** işareti. Komple iptal edilen adisyonun
  satırlarında bu alan NULL kalır — bu yüzden tek başına yeterli iptal filtresi değildir.
- `deleted_at`: Satırın kapandığı (ödendiği) an. Soft-delete değil, checkout zaman damgasıdır.

### 1.4 İki iptal mekanizması (kritik)

| Mekanizma              | İşaret                       | Rapordan dışlama koşulu                  |
| ---------------------- | ---------------------------- | ---------------------------------------- |
| Kısmi iptal (sipariş)  | `orders.canceller_id` dolu   | `whereNull('o.canceller_id')`            |
| Komple iptal (adisyon) | `sales.status = 'cancelled'` | `sales` join + `s.status != 'cancelled'` |

Sipariş bazlı raporlarda **her ikisi birden** uygulanmalıdır. Referans desen `maliReport`'tur.

---

## 2. Ortak Finansal Tanımlar

### 2.1 Net Satış (tahsilat)

Veritabanındaki hazır `net_sales` sütunu **kullanılmaz**. Net her yerde 7 tahsilat kanalının
toplamı olarak hesaplanır (`FinancialDay::NET_COLLECTIONS_SQL` sabiti ve `net_collections`
accessor'ı):

```
Net = Nakit + K.Kartı + Online + Ticket + Sabee + Getir + Yemeksepeti
```

- `credit_checkouts` (vadeli tahsilat) **hariç**: tahsilat anında POS zaten kanal sütunlarına
  yansıyan bir transaction üretir; ayrıca eklemek çift sayımdır.
- `credit_sales` (vadeli satış) **hariç**: alacak (memo) kalemidir, tahsilat değildir.

### 2.2 Brüt Satış

Poscloud'un hesabıyla (28.06.2026 verisiyle doğrulanmıştır):

```
Brüt = Net + Vadeli Satış + İndirim + İkram        (İptal DAHİL DEĞİLDİR)
```

`cancel` sütunu bilgi amaçlıdır; brüt toplamın bileşeni değildir.

---

## 3. Rapor Rapor Analiz

### 3.1 Kasa Raporu (Finansal Gün) — `reports/financial-day`

- **Amaç:** Ay bazında gün gün kasa özeti; tahsilat kanalları, kesinti kalemleri ve adisyon adedi.
- **Kaynak:** Yalnızca `financial_days` sütunları. Hiçbir değer sipariş seviyesinden hesaplanmaz.
- **Zaman filtresi:** `whereYear/whereMonth('day')` — seçilen ayın finansal günleri
  (açık gün dahil).
- **Net Satış sütunu:** `net_collections` accessor'ı (bkz. §2.1).
- **Excel:** `FinancialDayExport`, dosya adı işletme adı (`selected_pos_note`) + ay içerir.

### 3.2 Mali Rapor (KDV Kırılımlı) — `reports/mali-rapor`

- **Amaç:** Denetime hazır, vergi oranlarına göre gruplanmış dönem raporu.
  Dönemler: gün / ay / çeyrek / yarı yıl / yıl.
- **Dönem toplamları:** Kasa raporuyla aynı kaynaktan (`financial_days` sütun toplamları) —
  iki rapor tanım gereği birebir eşleşir.
- **KDV kırılımı (`buildVatBreakdown`):** Net satış (tahsilat), sipariş satırlarından türetilen
  dilim ağırlıklarına göre %0/%10/%20 dilimlerine dağıtılır:
    - Dilim oranı satış anındaki satırdan türetilir: `ROUND(100 * o.vat / o.price)`
      (ürünün güncel `vat_pct`'i kullanılmaz → geçmiş oran değişiklikleri doğru dilime düşer).
    - Ağırlıklar iptal ve ikram **hariç** brüt sipariş tutarlarıdır
      (`canceller_id IS NULL`, `s.status != 'cancelled'`, `payment_type != İKRAM`).
    - Fiyatlar KDV dahil olduğundan: `KDV = net_dilim × oran / (100 + oran)`.
    - Sonuç: KDV dağılımının toplamı = "Toplam Net Satış" (kuruş mutabık).
- **Ödeme dağılımı (`buildPaymentBreakdown`):** Yalnızca 7 gerçek tahsilat kanalı,
  `financial_days` sütun toplamlarından. Vadeli satış/tahsilat satır olarak yer almaz (§2.1).
- **Zaman filtresi:** Finansal günler `day` aralığıyla seçilir; KDV kırılımı sorgusu
  `s.financial_day_id IN (...)` ile aynı günlere bağlanır.

### 3.3 Ürün Bazlı Satış Raporu — `reports/sales` (Livewire)

- **Amaç:** **Ürün performansı raporu.** Filtrelenen finansal gün(ler)de satılan ürün
  adetleri × liste/satış fiyatları. Ciro/tahsilat raporu DEĞİLDİR.
- **Bilinçli tasarım kararları:**
    - İkram edilen ürünler "satılan ürün" sayılır ve **liste fiyatıyla** toplama girer
      (üretim/stok perspektifi). İkramın finansal etkisi kasa/mali raporda izlenir.
    - İndirim raporu **hiç etkilemez**; her birim liste fiyatından sayılır.
    - KDV, ödeme tipi, net gelir gibi tahsilat kavramları rapordan çıkarılmıştır.
- **Sütunlar:** Ürün Adı | Kategori | Birim Fiyat | Satış Adedi | Toplam
    - `Toplam = SUM(o.total_price)`, `Satış Adedi = SUM(o.quantity)`
    - `Birim Fiyat = Toplam / Adet` → dönem içinde fiyat değiştiyse **ağırlıklı ortalama**
      liste fiyatıdır; menü fiyatından sapması indirim değil, fiyat değişikliği/kanal farkıdır.
- **Filtreler ve sorgu kuralları (`loadData`):**
    - `o.status != 'cart'`
    - `o.canceller_id IS NULL` (kısmi iptal dışı)
    - `sales` join + `s.status != 'cancelled'` (komple iptal dışı)
    - Zaman: finansal gün eşleşmesi varsa `s.financial_day_id IN ($financialDayIds)`;
      yalnızca "Özel" tarih aralığında `o.deleted_at BETWEEN` penceresine düşülür.
- **`$financialDayIds` yaşam döngüsü:**
    - Tekil finansal gün seçimi → `[id]`
    - Bu hafta / bu ay / geçen ay / son 3 ay → dönemdeki tüm günlerin id listesi
    - Özel tarih aralığı, fallback ve manuel tarih değişikliği → boşaltılır
- **Excel:** `SalesReportExport` — görünümle aynı 5 sütun.

---

## 4. Mutabakat Kuralları

### 4.1 Raporlar arası eşitlikler

Aynı dönem için:

```
Satış Raporu Toplam  =  Kasa Brüt  =  Mali Rapor Brüt
Kasa Net             =  Mali Rapor Net  =  KDV kırılımı toplamı  =  Ödeme dağılımı toplamı
Kasa Brüt            =  Kasa Net + Vad.Satış + İndirim + İkram
```

Doğrulanmış örnek (28.06.2026):

```
Net 73.474,00 + Vad.Satış 5.950,00 + İndirim 3.011,00 + İkram 6.990,00 = Brüt 89.425,00
Satış Raporu Toplam = 89.425,00   (İptal 250,00 hiçbir toplama dahil değildir)
```

### 4.2 Zaman sınırı kuralı

Tüm raporlar **finansal gün sınırları** içinde üretilmelidir; takvim günü (00:00–23:59)
kullanılmaz. Sipariş seviyesinden okuyan her sorgu, zaman filtresini
`s.financial_day_id` üzerinden kurmalıdır. `deleted_at BETWEEN` yalnızca finansal gün
eşleşmesi mümkün olmayan serbest tarih aralıklarında kabul edilebilir; sınır günlerinde
komşu güne ait satışların sızmasına açıktır (28.06'da +799,98 ₺ sapmanın nedeni buydu).

### 4.3 KDV neden rapordan rapora farklıdır?

- Satırdaki `o.vat` **liste fiyatının** KDV'sidir (indirim öncesi, ikram dahil).
  Bu yüzden ham `SUM(o.vat)`, kasadaki fiili KDV'den yüksek çıkar.
- Fiili KDV yalnızca tahsilat seviyesinde bilinir: mali rapor bunu net satışı dilimlere
  dağıtıp dilim oranından **yeniden hesaplayarak** üretir (§3.2).
- Ürün bazlı satış raporu bu nedenle KDV sütunu içermez.

---

## 5. Sık Karşılaşılan Tutarsızlıklar ve Teşhis

| Belirti                             | Muhtemel neden                                                                           | Kontrol                                                            |
| ----------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Satış Raporu > Kasa Brüt            | Komple iptal adisyonlar dışlanmamış veya `deleted_at` penceresi komşu günden satır almış | `s.status != 'cancelled'` filtresi ve `financial_day_id` kullanımı |
| Satış Raporu < Kasa Brüt (dönemsel) | Açık finansal gün: koleksiyon `max('ending')` NULL'ları atlar, gün dışarıda kalır        | Dönemde açık gün varsa bitişi `now()` kabul et                     |
| KDV toplamları uyuşmuyor            | Ham `SUM(o.vat)` ile `financial_days.vat` / dilim KDV'si karşılaştırılıyor               | §4.3 — aynı tanımlar karşılaştırılmalı                             |
| Net ≠ kanal toplamı                 | DB `net_sales` sütunu okunmuş                                                            | Her zaman `net_collections` / `NET_COLLECTIONS_SQL` kullan         |
| Birim fiyat menü fiyatından farklı  | Dönem içi fiyat değişikliği veya kanal fiyat farkı (indirim DEĞİL)                       | Ürünün fiyat geçmişi / kanal kırılımı                              |

### Teşhis sorgusu: adisyon satırları liste fiyatı mı?

```sql
SELECT s.id, s.gross_price, s.discount, s.net_price,
       SUM(o.total_price) AS satir_toplami
FROM sales s
JOIN orders o ON o.sale_id = s.id AND o.canceller_id IS NULL
WHERE s.financial_day_id = :gun_id
  AND s.discount > 0
  AND s.status != 'cancelled'
GROUP BY s.id;
-- Beklenen: satir_toplami = gross_price (satırlar liste fiyatıdır)
```

---

## 6. Değişiklik Geçmişi (Temmuz 2026 mutabakat çalışması)

1. **Komple iptal dışlaması:** `SalesReportComponent` sorgularına `sales` join'i ve
   `s.status != 'cancelled'` filtresi eklendi (yalnızca `canceller_id` yetersizdi).
2. **Finansal gün kaynaklı filtreleme:** `deleted_at BETWEEN` yerine
   `s.financial_day_id IN (...)` desenine geçildi; özel tarih aralığı için pencere korunuyor.
3. **Rapor sadeleştirme:** Satış raporu ürün performansı tanımına indirildi;
   `AVG(price)`, `SUM(vat)`, ödeme tipi ve "Net Gelir" (hatalı `AVG×SUM` formülü) kaldırıldı.
   Excel export aynı 5 sütuna hizalandı.
