# Tekrar Eden Ödemeler (RecurringMovement) — Aktivasyon ve UI Planı

> Oluşturulma: 2026-08-12
> Durum: **Faz 1 + Faz 2 + Faz 3 tamamlandı (2026-08-12)** — CRUD UI, rapor ekranı, dashboard widget'ı, kur dönüşümü ve zamanlanmış iş hazır. Kalan: kategori yönetimi (Açık Soru 1) ve feature testler.

## 1. Mevcut Durum Analizi

| Öğe                                                    | Durum                                                                                                                                                        |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `app/Models/RecurringMovement.php`                     | ✅ Model hazır (fillable, casts, SoftDeletes, `currentTeamScope`)                                                                                            |
| `app/Http/Controllers/RecurringMovementController.php` | ⚠️ Boş sınıf, hiçbir metot yok                                                                                                                               |
| Route (`routes/web.php`)                               | ❌ Hiç tanımlı değil                                                                                                                                         |
| View / UI                                              | ❌ Hiç yok                                                                                                                                                   |
| Migration                                              | ⚠️ Repo'da `recurring_movements` migration'ı yok (`bank_accounts` için de yok → veritabanı ortak/uzak olabilir). Tablonun varlığı doğrulanmalı.              |
| Tek aktif kullanım                                     | `app/Livewire/BankAccountStatements.php` — banka hesabı detayında ekstre kayıtları (`type='statement'`, `period='onetime'`, `relation_id = bank_account.id`) |

### Modeldeki mevcut alanlar

- `type` → `statement` (mevcut kullanım), önerilen yeniler: `payment` (gider), `income` (gelir)
- `period` → `onetime` (mevcut), önerilen yeniler: `daily`, `weekly`, `monthly`, `quarterly`, `yearly`
- `status` → `active` varsayılan; önerilen: `active`, `paused`, `ended`
- `relation_id` → banka hesabına referans (ekstre kullanımında)
- `category_id` → kategorilendirme için ayrılmış, henüz kategori tablosu yok
- `start_date`, `end_date`, `next_date`, `amount`, `currency`, `title`, `description`
- `is_active` → boolean bayrak

## 2. Hedef

1. **Liste/Yönetim Ekranı:** Tekrar eden ödemelerin (kira, maaş, abonelik, taksit vb.) listelendiği, oluşturulduğu, düzenlendiği ve pasife alındığı bir CRUD ekranı.
2. **Maliyet Raporu Ekranı:** Belirli bir periyot (aylık / çeyreklik / yıllık / özel aralık) için tekrar eden ödemelerin toplam maliyetini gösteren rapor.
    - Örnek soru: _"İşyeri kirası için yıllık ne kadar ödeme yapıyoruz/yapacağız?"_ → aylık 10.000 TL × 12 = 120.000 TL.

## 3. Temel Tasarım Kararları

### 3.1. Occurrence (tekrar) üretimi

Tekrarlar veritabanında ayrı satır olarak tutulmayacak; raporlama sırasında **uçuşta (on-the-fly) hesaplanacak**:

- Verilen `[from, to]` aralığında, `start_date` ile `min(end_date, to)` arasında kalan tüm periyot adımları üretilir.
- Her adım `amount` tutarındadır; toplam = adım sayısı × tutar.
- `period='onetime'` kayıtlar yalnızca `start_date` aralığa düşüyorsa sayılır.

> Faz 2'de isteğe bağlı olarak gerçekleşen ödemelerin `BankTransaction`'a malzeme edilmesi (materialization) eklenebilir; Faz 1'de gerekmez.

### 3.2. Kategori yaklaşımı

`category_id` için ayrı tablo kurmak yerine Faz 1'de basit, sabit bir kategori listesi kullanılacak (örn. `rent`, `salary`, `subscription`, `tax`, `other`). Değerler modelde sabit olarak tanımlanır, `category_id` alanı Faz 1'de boş bırakılabilir veya ileride `categories` tablosuna bağlanır. **Açık soru → Bölüm 8.**

### 3.3. Mevcut ekstre verisiyle uyum

`BankAccountStatements` üzerinden yazılmış `type='statement'` kayıtlar raporda ayrı bir grup olarak gösterilecek; yeni CRUD ekranı varsayılan filtrede `type != 'statement'` gösterecek, banka hesap detayındaki mevcut davranışa dokunulmayacak.

## 4. Dosya Değişiklik Planı

### 4.1. Migration / doğrulama

- `php artisan tinker` ile `Schema::hasTable('recurring_movements')` kontrolü.
- Tablo yoksa güvenlik ağı migration eklenir: `database/migrations/xxxx_create_recurring_movements_table.php` (alanlar modeldeki fillable listesiyle birebir + `softDeletes`).

### 4.2. Model genişletmeleri — `app/Models/RecurringMovement.php`

```php
public const PERIODS = ['onetime', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly'];
public const CATEGORIES = ['rent' => 'Kira', 'salary' => 'Personel', 'subscription' => 'Abonelik', 'tax' => 'Vergi', 'other' => 'Diğer'];

public function bankAccount()          // relation_id -> BankAccount (type=statement için)
public function occurrencesBetween(Carbon $from, Carbon $to): Collection  // tarih listesi
public function projectedTotal(Carbon $from, Carbon $to): float           // adım sayısı × amount
public function scopeActive($q)        // is_active = 1 ve status = 'active'
```

### 4.3. Controller — `app/Http/Controllers/RecurringMovementController.php`

Metotlar: `index`, `create`, `store`, `edit`, `update`, `destroy` (soft delete), `toggle` (aktif/pasif), `report`.

- `store`/`update` validasyonu: `title` zorunlu, `amount` numeric > 0, `period` in:PERIODS, `start_date` zorunlu, `end_date` opsiyonel (`null` = süresiz), `currency` in:TRL/USD/EUR.
- Kayıt sırasında `next_date = start_date` atanır.

### 4.4. Rapor mantığı — `report()`

- Girdi: `from`, `to` (varsayılan: içinde bulunulan yıl) veya hazır kısayollar (`this_month`, `this_quarter`, `this_year`, `custom`) — mevcut `reports/company-and-invoice` ekranındaki kısayol deseni takip edilecek.
- Çıktı:
    - Ödeme kalemi bazında satırlar: başlık, periyot, birim tutar, aralıktaki adım sayısı, **toplam**.
    - Ay bazında dağılım (aylık kolon toplamı).
    - Genel toplam (para birimi bazında ayrı; TL dönüşümü Faz 3 adayı, `ExchangeRate` modeli mevcut).

### 4.5. Route'lar — `routes/web.php` (auth grubu içine)

```php
Route::prefix('recurring-movements')->name('recurring-movements.')->group(function () {
    Route::get('/index',   [RecurringMovementController::class, 'index'])->name('index');
    Route::get('/report',  [RecurringMovementController::class, 'report'])->name('report');
    Route::get('/create',  [RecurringMovementController::class, 'create'])->name('create');
    Route::post('/store',  [RecurringMovementController::class, 'store'])->name('store');
    Route::get('/{id}/edit',    [RecurringMovementController::class, 'edit'])->name('edit');
    Route::patch('/{id}/update',[RecurringMovementController::class, 'update'])->name('update');
    Route::patch('/{id}/toggle',[RecurringMovementController::class, 'toggle'])->name('toggle');
    Route::get('/{id}/destroy', [RecurringMovementController::class, 'destroy'])->name('destroy');
});
```

### 4.6. View'lar — `resources/views/recurring-movements/`

- `index.blade.php` → liste tablosu (başlık, tip, periyot, tutar, para birimi, sonraki tarih, durum, aksiyonlar) + filtreler (tip, durum) + "Yeni Kayıt" butonu.
- `_form.blade.php` → create/edit ortak form partial.
- `create.blade.php` / `edit.blade.php`.
- `report.blade.php` → periyot kısayol butonları + tarih aralığı seçici + özet kartları + kalem bazlı tablo + ay bazlı dağılım.
- Stil: mevcut ekranlardaki Tailwind desenleri (stone/teal paleti, `x-main-button` bileşeni) kullanılacak.

### 4.7. Navigasyon

- `resources/views/partials/navigation-bar.blade.php` içine "TEKRAR EDEN ÖDEMELER" ve "MALİYET RAPORU" girişleri (`x-main-button` deseniyle).

## 5. Faz Planı ve Görev Listesi

### Faz 0 — Doğrulama (ön koşul)

- [ ] `recurring_movements` tablosunun veritabanında var olduğunu doğrula (`Schema::hasTable`).
- [ ] Yoksa migration oluştur ve `php artisan migrate` çalıştır.
- [ ] Mevcut `type='statement'` kayıtlarını incele (yeni ekranı kirletmemesi için filtre stratejisini teyit et).

### Faz 1 — CRUD UI ✅ (2026-08-12)

- [x] Model: sabitler, `bankAccount()` ilişkisi, `scopeActive`, `nextOccurrenceDate()` (store/update için next_date hesabı).
- [x] Controller: `index`, `create`, `store`, `edit`, `update`, `destroy`, `toggle` + validasyon (`validateMovement`).
- [x] Route'lar.
- [x] View'lar: `index`, `_form`, `create`, `edit`.
- [x] Navigasyona menü girişi (dashboard → Finansal Hareketler → "TEKRAR EDEN ÖDEMELER" butonu, `$count_recurring`).
- [x] Unit test: `nextOccurrenceDate()` için 3 senaryo (gelecek başlangıç, geçmiş başlangıç, bitmiş kayıt).
- [ ] Manuel test: kayıt oluştur → listele → düzenle → pasife al → sil (canlı ortamda yapılacak).

**Faz 1 Notları:**

- `store`/`update` sonrası `next_date` otomatik hesaplanır: başlangıç ilerideyse başlangıç tarihi, geçmişteyse bugünden sonraki ilk tekrar. `end_date` geçmişte kalan kayıt `status='ended'` olarak yeniden aktifleşmez; gelecek tekrarı varsa otomatik aktifleşir.
- `statement` tipi formdan oluşturulamaz (ekstre kayıtları BankAccountStatements akışına aittir); listede görüntülenebilir ama düzenle/sil/toggle yine de mümkündür.
- Silme soft-delete; toggle yalnızca `is_active` alanını çevirir, `scopeActive` sayesinde rapor ve zamanlayıcıdan düşer.
- **Kategori:** `CATEGORIES` sabitine `credit` (Kredi), `statement` (Ekstre), `invoice` (Fatura), `service` (Hizmet) eklendi; `category_id` string anahtar saklar (cast `integer` → `string` yapıldı, ReceiptController'daki `in:income,expense` deseniyle uyumlu). ⚠️ DB kolonu integer ise `ALTER TABLE recurring_movements MODIFY category_id VARCHAR(50) NULL;` gerekir.
- **İşletme bağlantısı:** `relation_id` artık kullanıcı kayıtlarında Company'ye işaret eder (statement kayıtlarında BankAccount olmaya devam eder). Formda "Mevcut işletme seç" veya "Yeni işletme oluştur" (select or create); yeni işletme `CompanyController@store` varsayılanlarıyla oluşturulur (ödeme → supplier, tahsilat → client). Liste ekranında İşletme kolonu ve kategori rozeti var.

### Faz 2 — Maliyet Raporu ✅ (2026-08-12)

- [x] Model: `occurrencesBetween()`, `projectedTotal()` — çapa tabanlı indeks yaklaşımı; ay sonu kayması yok, artık yıl ve süresiz kayıtlar destekli.
- [x] Model: `PERIODS`, `PERIOD_LABELS`, `CATEGORIES` sabitleri, `scopeActive`, `bankAccount()` ilişkisi, `period_label` accessor.
- [x] Controller: `report()` — aralık kısayolları (`this_month`, `this_quarter`, `this_year`, `next_12_months`, `custom`) + tip filtresi (`payment` varsayılan / `statement` / `all`).
- [x] Route: `GET /recurring-movements/report` → `recurring-movements.report`.
- [x] View: `resources/views/recurring-movements/report.blade.php` — para birimi bazında özet kartlar, kalem tablosu, ay bazlı dağılım, özel aralık formu, yazdırma.
- [x] Örnek veri: `database/seeders/RecurringMovementSeeder.php` (idempotent; kira, maaş, abonelik, sigorta, muhasebe).
- [x] Örnek senaryo testi: aylık 10.000 TL kira, yıllık rapor → 120.000 TL (unit test ile doğrulandı).

### Faz 3 — İyileştirmeler ✅ (2026-08-12)

- [x] Dashboard'a "Yaklaşan Tekrar Eden Ödemeler" widget'ı (önümüzdeki 30 gün, `RecurringMovement::upcomingOccurrences()`) + Raporlar bölümüne "MALİYET RAPORU" butonu.
- [x] TL dışı kayıtlar için `ExchangeRate` ile "Tahmini TL karşılığı" özet kartı; kuru bulunamayan para birimleri raporda not edilir.
- [x] Zamanlanmış iş: `recurring:process-due` komutu (`--dry-run` destekli) — vadesi gelen kayıtlar için taslak `BankTransaction` (`is_active=0`, `payment_type='recurring'`) oluşturur, `next_date`'i ilerletir, `end_date`/tek seferlik kayıtları `ended` yapar; tekrar koruması mevcut. `Kernel.php`'de her gün 06:00'da çalışır.
- [ ] `category_id` için gerçek kategori yönetimi (gerekirse ayrı tablo) — Açık Soru 1'e yanıt bekliyor.

### Faz 4 — Testler

- [ ] Feature test: CRUD + yetki (auth) kontrolleri — `tests/Feature/RecurringMovementTest.php`.
- [x] Unit test: `occurrencesBetween` ve `projectedTotal` uç durumları — `tests/Unit/RecurringMovementOccurrenceTest.php` (Faz 2 ile birlikte tamamlandı).

## 6. Uç Durumlar / Dikkat Edilecekler

- **Ay sonu tekrarları:** 31'inde başlayan aylık kayıt, 30 çeken aylarda ayın son gününe oturtulmalı (Carbon `addMonthNoOverflow`).
- **Süresiz kayıtlar:** `end_date = null` → rapor aralığının sonuna kadar sayılır.
- **Rapor aralığı dışında başlayan ama süren kayıtlar:** `start_date < from` olsa bile aralık içindeki adımlar sayılmalı.
- **Pasif kayıtlar:** raporda varsayılan olarak hariç, UI'da "pasifleri dahil et" seçeneği.
- **Çoklu para birimi:** toplam satırları para birimi bazında ayrı gösterilmeli; tek para birimine zorla dönüştürülmemeli (Faz 3'e kadar).
- **Takım izolasyonu:** tüm sorgular `currentTeamScope` üzerinden zaten filtreli; rapor sorgusunda da korunmalı.

## 7. Fark Edilen Küçük Hatalar (bu kapsamda düzeltilebilir)

- `BankAccountStatements::$rules` içinde `'description' => 'required|numeric'` kuralı var; alan UI'da "Asgari Tutar" olarak kullanılıyor. Alan adı ile kural tutarlı ama kafa karıştırıcı — `min_amount` alanına taşınması değerlendirilebilir.
- `RecurringMovement` modelinde `is_active` fillable'da ama ekstre oluşturma akışında hiç set edilmiyor (DB varsayılanına kalır). CRUD'da açıkça yönetilecek.

## 8. Açık Sorular

1. Kategori yapısı sabit liste olarak yeterli mi, yoksa kullanıcı tanımlı kategoriler (ayrı tablo) isteniyor mu?
2. Gerçekleşen tekrar eden ödemeler otomatik olarak `BankTransaction` kaydı oluşturmalı mı (Faz 3), yoksa manuel mi kalacak?
3. Rapor tek ekranda mı olsun, yoksa mevcut `reports/` bölümünün altına mı taşınsın (`reports/recurring-costs`)?
4. Gelir tipi (`income`) tekrar eden kayıtlar da ilk fazda desteklenecek mi, yoksa yalnızca gider/ödeme mi?

## 9. Faz 2 Uygulama Notları (2026-08-12)

- **Occurrence hesabı:** Zincirleme tarih artırma yerine `start_date` çapasından indeks tabanlı üretim yapıldı (`occurrenceAt(n)`). Böylece 31 Ocak'ta başlayan aylık kayıt Şubat'ta 28'e, Mart'ta tekrar 31'e oturuyor; kayma (drift) oluşmuyor.
- **Fast-forward:** `estimateIndex()` ile rapor aralığına sıçrama yapılıyor; bir indeks geriden tekrar adım atıldığı için tahmin hatası tekrar kaybettirmiyor.
- **`is_active` null toleransı:** Ekstre kayıtları `is_active` set etmediğinden `scopeActive` null değerleri aktif sayıyor.
- **Rapor erişimi:** Navigasyon girişi Faz 1'e bırakıldı; rapor ekranına `/recurring-movements/report` adresinden ulaşılıyor.
- **Örnek veri:** `php artisan db:seed --class=RecurringMovementSeeder` ile oluşturulabilir (mevcut kayıtlarla aynı başlıkları atlar).

### Faz 3 notları

- **Taslak işlem güvenliği:** Zamanlanmış işin oluşturduğu `BankTransaction` kayıtları `is_active=0` (taslak) olduğundan kullanıcı onaylayana kadar bakiye hesaplarını etkilemez.
- **Çoklu ekip:** Konsolda oturum olmadığı için `currentTeamScope` devre dışı kalır; komut tüm ekiplerin vadesi gelen kayıtlarını işler, her taslağa kendi `current_team_id`'si yazılır.
- **Kur kaynağı:** `ExchangeRate` tablosundaki en güncel kayıt (`date` + `id` sıralı) kullanılır; dönüşüm yalnızca özet kartındadır, kalem tutarları kendi para biriminde kalır.
- **Manuel çalıştırma:** `php artisan recurring:process-due --dry-run` ile önce prova yapılabilir.
