feat(dönem): dönem kapatma, devir ve düzeltme için yeniden açma (backend)

- POST /periods/{id}/close: tek transaction; açık → borç, artı → birikim + devir
- Devir sonraki dönem açılınca ona bağlanır
- POST /periods/{id}/reopen (sahip): düzeltme modu, yeni dönem açık kalır

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 12:20:09 +03:00
parent 8ec1fd6f25
commit 62e92f552a
17 changed files with 724 additions and 26 deletions
+3 -2
View File
@@ -1,7 +1,7 @@
# 🧠 Proje Hafızası
## ▶️ Sıradaki Adım
**Faz 6 – Adım 2:** Backend dönem kapatma (`POST /periods/{id}/close`, tek transaction): `status=closed`, `closed_at/by`; gerçekleşen birikim < 0 → `carryovers{kind: debt}`, > 0 → kullanıcının `to_savings` kadarı birikime (Faz 8'e kadar dönemde saklanır), kalanı `surplus`; devrin `to_period_id`'si sonraki dönem açılınca bağlanır. Yeniden açma (sahip): devirler soft delete. **Kullanıcıya sorulacak:** başka açık dönem varken eski dönem nasıl yeniden açılır?
**Faz 6 – Adım 3:** Frontend Değerlendirme sayfası (`/degerlendirme`, açık dönem; ileride `?donem=`): 4 soru kartı (`PeriodReview`), grup planlanan/gerçekleşen çubukları, en çok harcanan kategoriler (`useCategories(true)` ile ad), haftalık dağılım, "Nasıl daha iyi yapabilirim?" (Markdown editör → `PATCH reflection_md`), notlar `NotesSection context="review"` (yalnızca başlık listesi, tıklayınca açılır), "🔒 Dönemi Kapat" düğmesi (sihirbaz Adım 4).
**Bekleyen (kullanıcıda):** Faz 5 – Adım 5 canlı yedek kurulumu (`docs/DEPLOY.md` §6.1–6.3) → çıktılar gelince Faz 5 ✅.
## 📌 Alınan Kararlar
@@ -51,7 +51,7 @@
- **Canlı yedekleme kurulumu bekliyor** (betikler hazır, `docs/DEPLOY.md` §6) → gerçek veri girişinden önce.
- **Canlı MongoDB auth kapalı** (Dokploy replica set betiği keyFile kullanmıyor, özel Command'ı yok sayıyor). İç ağda, port kapalı. Ayrıntı: `docs/DEPLOY.md` §4.
- Üye çıkarma / rol değiştirme / sahipliği devretme henüz yok (Ayarlar ekranıyla birlikte, Faz 2+).
- Kapalı dönem yeniden açılırken başka açık dönem varsa davranış (Faz 6).
- ~~Kapalı dönem yeniden açma~~ → çözüldü: düzeltme modu (`correcting`). Arayüzde düzeltmedeki dönem için Defter/Plan dönem seçimi Faz 6 Adım 5'te.
- Altın kuru için veri kaynağı seçimi (Faz 8).
- Aylık değerlendirme çıktı formatı kullanıcıdan gelecek (Faz 9).
- Canlı MongoDB kurulum şekli (konteyner mi, doğrudan mı) Faz 3'te birlikte kontrol edilecek.
@@ -108,3 +108,4 @@
### 2026-11-12 — Faz 6
- **Adım 1:** Değerlendirme backend: `services/review.rs` (`budget::summary` + `budget::view` + kategori aggregation), `routes/review.rs` → `GET /periods/{id}/review` (`PeriodReview`: `available` = gelir − sabit − borç + artı, `savings_goal`, `spent_total`, `realized_savings` = available − harcanan, `savings_gap`; `groups` planned/spent, `categories` çoktan aza (+count), `weeks` 4 grubun toplamı). Kapalı dönemde okunur. `periods.reflection_md` (PATCH, ≤ 20.000 karakter, `""` → null, yalnızca açık dönem). `tests/review.rs` (3). Mutasyon: 4. soruya hedef düşmek / silinenleri saymak → yakalandı. Not: `TestApp::register` adı e-postaya çevirir → Türkçe karakterli ad kullanma.
- **Adım 2:** Kapatma backend: `services/closing.rs` — `settle(realized, to_savings)` (< 0 → borç, birikim istenemez; ≥ 0 → varsayılan tamamı birikime, kalan artı), `close` (tek transaction: dönem `closed` + `closed_at/by` + `to_savings`, bu dönemden eski devirler soft delete, yeni devir; `to_period_id` = sonraki dönem ya da null), `reopen` (sahip; `closed` → **`correcting`**; ardından kapalı/düzeltmede dönem varsa 409). `PeriodStatus::Correcting` + `is_writable()` → `get_open`/`update` buna bakar; `current()` yalnız `open`. `period::create` artık transaction: önceki dönemin bağsız devrini bağlar. `POST /periods/{id}/close` (`PeriodCloseRequest`/`PeriodCloseResult`/`Carryover`), `POST /periods/{id}/reopen`. Period DTO: `to_savings`, `closed_at`. `tests/closing.rs` (4) + `settle` birim testi. Mutasyon 3/3 yakalandı; hata zorlanınca transaction geri aldı (dönem açık, sürüm aynı). Backend 113 test. Karar: yeniden açma = düzeltme modu (kullanıcı "a").