diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md new file mode 100644 index 0000000..d897b2a --- /dev/null +++ b/docs/DATA_MODEL.md @@ -0,0 +1,228 @@ +# 🗄️ Veri Modeli (v1) + +> Durum: ✅ **Onaylandı (2026-11-11)** · Veritabanı: MongoDB 7 (replica set) · Şema sürümü: `1` + +## 1. Genel Kurallar + +### 1.1 Ortak alanlar (her koleksiyonda) +| Alan | Tip | Açıklama | +|---|---|---| +| `_id` | UUID v7 (BSON Binary, subtype 4) | Zamana göre sıralanabilir; çevrimdışı cihaz da üretebilir (Faz 11 senkronizasyon) | +| `household_id` | UUID | Hane izolasyonu. **Her sorguda zorunlu filtre** | +| `created_at` | DateTime (UTC) | | +| `updated_at` | DateTime (UTC) | Senkronizasyonda "bundan sonra değişenler" sorgusu | +| `deleted_at` | DateTime \| null | Soft delete. Normal sorgular `deleted_at: null` filtreler | +| `version` | i32 | Her güncellemede +1. Çakışma kontrolü (iki kişi aynı kaydı düzenlerse) | + +İstisna: `users` hane bilgisini `household_id` alanında tutar. `exchange_rates` ve `schema_migrations` haneye bağlı değildir (bkz. ilgili bölüm). + +### 1.2 Sayısal değerler — float **yok** +| Değer | Saklama | Örnek | +|---|---|---| +| Para (TL) | `i64` **kuruş** | `1.250,00 ₺` → `125000` | +| Varlık miktarı | `i64` **milyonda bir birim** (`_e6`) | `12,5 gr` → `12500000` · `150 $` → `150000000` | +| Kur / birim fiyat | `i64` **milyonda bir TL** (`_e6`) | `1 USD = 41,2345 TL` → `41234500` | + +- Hesaplamalarda taşmayı önlemek için ara çarpımlar `i128` ile yapılır: `toplam_kuruş = miktar_e6 × fiyat_e6 / 10¹⁰`. +- **Neden Decimal128 değil?** Rust sürücüsünde Decimal128 ile aritmetik yapılamıyor (önce dönüştürmek gerekiyor) ve aggregation toplamları tam sayılarda daha basit. Ölçekli tam sayı tek tip ve hızlı. + +### 1.3 Tarihler +- **Takvim tarihi** (harcama tarihi, dönem başı/sonu, plan kalemi tarihi): `"YYYY-MM-DD"` **metin** (yerel tarih). Saat dilimi kayması olmaz, metin sıralaması = tarih sıralaması. +- **Zaman damgası** (`created_at` vb.): BSON DateTime (UTC). +- Ekranda her zaman `GG.AA.YYYY`. + +### 1.4 Hesaplanan değerler saklanmaz +Gerçekleşen toplamlar, kalan tutar, hafta bütçeleri, kâr/zarar, ortalama maliyet → sorgu anında aggregation / `services/` ile hesaplanır. + +## 2. Sabit Tanımlar (kodda, veritabanında değil) + +**Ana gruplar** (`group` alanı): değiştirilemez, silinemez. + +| Kod | Ad | Emoji | Varsayılan % | +|---|---|---|---| +| `needs` | Zorunlu | 🏠 | 50 | +| `wants` | İstek | 🛍️ | 30 | +| `culture` | Kültürel | 📚 | 10 | +| `unexpected` | Beklenmeyen | ⚡ | 10 | + +Renkler ve ikonlar frontend tasarım token'larında. + +## 3. Koleksiyonlar + +### 3.1 `users` +```js +{ _id, household_id, email /* küçük harf, benzersiz */, password_hash /* argon2 */, + name, role: "owner" | "member", + preferences: { theme: "light" | "dark" | "system" }, + last_login_at, created_at, updated_at, deleted_at, version } +``` +- Bir kullanıcı **tek bir haneye** üyedir. + +### 3.2 `households` +```js +{ _id, name, owner_id, + settings: { + currency: "TRY", + budget_split_pct: { needs: 50, wants: 30, culture: 10, unexpected: 10 }, + budget_suggestion: "previous" | "avg3" | "default" // varsayılan öneri yöntemi + }, + created_at, updated_at, deleted_at, version } +``` +- Prompttaki `settings` koleksiyonu yerine **haneye gömülü** (her hanede tek kayıt olduğu için ayrı koleksiyona gerek yok). + +### 3.3 `invites` +```js +{ _id, household_id, code /* 8 karakter, benzersiz */, created_by, + expires_at, used_by: null | user_id, used_at, created_at, ... } +``` + +### 3.4 `periods` — Dönem +```js +{ _id, household_id, name /* "Aralık 2026" */, + start_date: "2026-12-01", end_date: "2027-01-01", // bitiş HARİÇ + status: "open" | "closed", + savings_goal: 500000, // kuruş — birikim hedefi + reflection_md: "...", // "Nasıl daha iyi yapabilirim?" + closed_at, closed_by, reopened_at, + previous_period_id, + created_at, updated_at, deleted_at, version } +``` +- **Bitiş tarihi hariç:** `01.12.2026 – 01.01.2027` → 1–31 Aralık. Dönemler arka arkaya boşluksuz ve çakışmasız eklenir. +- Hanede en fazla **bir** `open` dönem (servis kontrolü + kısmi benzersiz indeks). +- Yeniden açılan dönemin durumu tekrar `open` olur. O sırada başka açık dönem varsa önce o kapatılmalı mı, yoksa geçici olarak iki açık dönem mi olsun? → **Açık konu, Faz 6'da netleşecek.** +- **Haftalar saklanmaz.** `start_date`/`end_date`'ten hesaplanır (Pazartesi başlangıçlı; ilk/son kısmi haftalar ayrı). + +### 3.5 `categories` — Alt kategoriler +```js +{ _id, household_id, group: "needs", name: "Market", emoji: "🛒", + sort_order: 10, is_active: true, created_at, ... } +``` +- Silinmez, `is_active: false` ile pasife alınır. +- İlk kurulumda örnekler: **Zorunlu:** Market, Kira, Faturalar, Ulaşım · **İstek:** Dışarıda yemek, Giyim, Eğlence · **Kültürel:** Kitap, Eğitim, Sinema/Konser · **Beklenmeyen:** Sağlık, Tamir, Hediye + +### 3.6 `payment_methods` +```js +{ _id, household_id, name: "Kredi Kartı", emoji: "💳", + sort_order, is_active, created_at, ... } +``` +- İlk kurulumda: 💵 Nakit, 💳 Kredi Kartı, 🏧 Banka Kartı, 🔁 Havale/EFT + +### 3.7 `plan_items` — Sabit gelir / gider kalemleri +```js +{ _id, household_id, period_id, + kind: "income" | "expense", + name: "Maaş", amount: 4500000, date: "2026-12-05", + sort_order, copied_from_id /* önceki dönemden kopyalandıysa */, + created_at, ... } +``` + +### 3.8 `budgets` — Grup bütçeleri +```js +{ _id, household_id, period_id, group: "needs", + scope: "month" | "week", + week_start: null | "2026-12-07", // scope = week ise o haftanın ilk günü + amount: 1500000, + created_at, ... } +``` +- Her dönem × grup için **bir aylık** kayıt. +- Haftalık bütçe normalde **saklanmaz**, aylık tutardan gün sayısına göre hesaplanır. Yalnızca kullanıcı haftalık tutarı **elle değiştirirse** `scope: "week"` kaydı oluşur. + +### 3.9 `entries` — Harcamalar (günlük defter) +```js +{ _id, household_id, period_id, + date: "2026-12-09", amount: 34550, + group: "needs", category_id, payment_method_id, + description: "Haftalık market", + created_by /* user_id */, created_at, ... } +``` + +### 3.10 `carryovers` — Devirler +```js +{ _id, household_id, from_period_id, to_period_id, + kind: "debt" | "surplus", amount: 120000, + created_at, ... } +``` +- Dönem kapanışında oluşur. Dönem yeniden açılırsa soft delete edilir ve kapanışta yeniden hesaplanır. + +### 3.11 `notes` +```js +{ _id, household_id, period_id, + context: "planning" | "review", + title, body_md, created_by, created_at, ... } +``` + +### 3.12 `assets` — Varlıklar +```js +{ _id, household_id, + kind: "cash" | "gold" | "fx", + code: "TRY" | "XAU_GRAM" | "XAU_CEYREK" | "XAU_YARIM" | "XAU_TAM" | "XAU_ONS" | "USD" | "EUR" | ..., + name: "Gram Altın", unit_label: "gr", + is_active, created_at, ... } +``` + +### 3.13 `asset_transactions` — Alım / satım +```js +{ _id, household_id, asset_id, + type: "buy" | "sell", + date: "2026-12-15", + quantity_e6: 5000000, // 5 gr + unit_price_e6: 3250000000, // 3.250,00 TL + total: 1625000, // kuruş (girilen gerçek tutar; komisyon farkı olabilir) + note, source_period_id /* dönem kapanışından geldiyse */, + created_at, ... } +``` +- Ortalama maliyet ve kâr/zarar bu kayıtlardan hesaplanır, saklanmaz. + +### 3.14 `exchange_rates` — Kurlar +```js +{ _id, household_id: null | id, // null = otomatik (tüm haneler), dolu = hanenin elle girdiği kur + code: "USD", rate_e6: 41234500, + source: "tcmb" | "gold_api" | "manual", + rate_date: "2026-12-15", fetched_at, + created_at, ... } +``` +- Güncel kur sırası: hanenin elle girdiği en yeni kur → en yeni otomatik kur. + +### 3.15 `schema_migrations` +```js +{ _id: 1 /* sürüm numarası */, name: "initial", applied_at } +``` +- Uygulama açılışında uygulanmamış migration'lar sırayla çalışır, ardından indeksler oluşturulur. + +## 4. İndeksler + +| Koleksiyon | İndeks | Tip | +|---|---|---| +| `users` | `email` | benzersiz | +| `invites` | `code` | benzersiz | +| `periods` | `(household_id, start_date)` | | +| `periods` | `(household_id)` where `status: "open"` | kısmi benzersiz | +| `categories`, `payment_methods` | `(household_id, sort_order)` | | +| `plan_items`, `budgets`, `notes` | `(household_id, period_id)` | | +| `entries` | `(household_id, period_id, date)` | | +| `entries` | `(household_id, date)` | raporlar için | +| `carryovers` | `(household_id, to_period_id)` | | +| `asset_transactions` | `(household_id, asset_id, date)` | | +| `exchange_rates` | `(code, household_id, rate_date desc)` | | +| *(tümü)* | `(household_id, updated_at)` | senkronizasyon (Faz 11) | + +## 5. Temel Hesaplamalar (servis katmanı) + +| Değer | Formül | +|---|---| +| **Harcanabilir tutar** | Σ gelir − Σ sabit gider − birikim hedefi − devreden borç + devreden artı | +| **Gerçekleşen harcama** | Σ `entries.amount` (dönem içi) | +| **Kalan harcanabilir** | Harcanabilir tutar − gerçekleşen harcama | +| **Haftalık bütçe** | aylık grup bütçesi × (haftanın dönem içindeki gün sayısı ÷ dönemin gün sayısı), elle girilmiş kayıt varsa o | +| **Gerçekleşen birikim** (4. soru) | Σ gelir − Σ sabit gider − devreden borç + devreden artı − gerçekleşen harcama | +| **Kapanışta devir** | Gerçekleşen birikim < 0 → `debt` · > 0 ise kullanıcı seçer: birikime aktar veya `surplus` olarak devret | +| **Ortalama maliyet** | Hareketli ağırlıklı ortalama: alımda güncellenir, satışta değişmez | +| **Kâr/zarar** | (eldeki miktar × güncel kur) − (eldeki miktar × ortalama maliyet) | + +## 6. Dönem Kapanışı (tek transaction) +1. `periods.status = "closed"`, `closed_at`, `closed_by` +2. `carryovers` kaydı (borç veya artı) → sonraki dönem +3. (Onaylıysa) gerçekleşen birikim → `asset_transactions` (`TRY`, `buy`, `source_period_id`) +4. (İstenirse) yeni dönem: `plan_items` ve aylık `budgets` kopyalanarak açılır + +Transaction'ın bir adımı hata verirse önceki adımlar da geri alınır, hiçbir şey değişmez. diff --git a/docs/MEMORY.md b/docs/MEMORY.md index bc7e76c..d0f1516 100644 --- a/docs/MEMORY.md +++ b/docs/MEMORY.md @@ -1,7 +1,7 @@ # 🧠 Proje Hafızası ## ▶️ Sıradaki Adım -**Faz 0 – Adım 5:** Veri modeli taslağı `docs/DATA_MODEL.md` → kullanıcı onayı. Onaydan sonra Faz 0 kapanır, Faz 1 başlar. +**Faz 1 – Adım 1:** `config.rs` (ortam değişkenleri), `db.rs` (MongoDB bağlantısı + ping), `error.rs` (Türkçe hata yanıtları), `/api/health` DB durumu. ## 📌 Alınan Kararlar - Sürüm kontrolü: Gitea (`https://gitea.ta1sph.info/suphicakir/kakeibo.git`), dal `main`. @@ -10,9 +10,11 @@ - Git kimlik bilgisi: libsecret (KWallet) credential helper + Gitea token. - Yerel MongoDB host portu 27020 (27017 sistem mongod, 27018/27019 başka projeler). - Frontend lint aracı: oxlint (Vite şablonu varsayılanı), ESLint yerine. +- Veri modeli v1 onaylandı (2026-11-11): ölçekli tam sayı (`_e6`), tarih `YYYY-MM-DD` metin, dönem bitişi hariç, kullanıcı = tek hane, ayarlar haneye gömülü, haftalık bütçe hesaplanır. - Sürümler: Rust 1.99.1 (edition 2024), Axum 0.8, React 19, Vite 8, TypeScript 7. ## ❓ Açık Konular +- Kapalı dönem yeniden açılırken başka açık dönem varsa davranış (Faz 6). - 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. @@ -24,3 +26,4 @@ - **Adım 2:** `.vscode/` (extensions, settings, tasks, launch), `.gitignore`, `.env.example` ve monorepo klasörleri oluşturuldu. `git init -b main`. - **Adım 3:** `CLAUDE.md`, `docs/MEMORY.md`, `docs/PHASES.md` oluşturuldu. Gitea remote eklendi, ilk commit push edildi. - **Adım 4:** Rust kuruldu. `docker-compose.dev.yml` (mongo:7, rs0, healthcheck ile otomatik `rs.initiate`). Backend: Axum, yalnızca `GET /api/health`. Frontend: Vite react-ts şablonu, `/api` proxy → 8080, geçici durum ekranı. Kök `package.json`: concurrently ile `npm run dev`. Uçtan uca test edildi (Mongo PRIMARY, proxy çalışıyor). +- **Adım 5:** `docs/DATA_MODEL.md` hazırlandı ve onaylandı. **Faz 0 tamamlandı.** diff --git a/docs/PHASES.md b/docs/PHASES.md index 6ec2f54..d85d565 100644 --- a/docs/PHASES.md +++ b/docs/PHASES.md @@ -4,8 +4,8 @@ | Faz | Kapsam | Çıktı | Durum | |---|---|---|---| -| **Faz 0** | Ortam kurulumu, VS Code yapılandırması, monorepo iskeleti, hafıza dosyaları, yerel MongoDB, veri modeli onayı | `npm run dev` ile boş uygulama tarayıcıda açılıyor | 🔄 | -| **Faz 1** | Backend temeli: config, DB bağlantısı, kimlik doğrulama, hane, kategoriler, ödeme türleri, health check | API `.http` dosyasıyla test edilebiliyor | ⏳ | +| **Faz 0** | Ortam kurulumu, VS Code yapılandırması, monorepo iskeleti, hafıza dosyaları, yerel MongoDB, veri modeli onayı | `npm run dev` ile boş uygulama tarayıcıda açılıyor | ✅ | +| **Faz 1** | Backend temeli: config, DB bağlantısı, kimlik doğrulama, hane, kategoriler, ödeme türleri, health check | API `.http` dosyasıyla test edilebiliyor | 🔄 | | **Faz 2** | Frontend temeli: tasarım sistemi, tema, logo/ikonlar, layout, navigasyon, giriş/kayıt | Giriş yapılabilen, temalı iskelet uygulama | ⏳ | | **Faz 3** | 🚀 İlk canlıya alma: Dockerfile, Dokploy, canlı MongoDB, domain + HTTPS, yedekleme | Giriş ekranı canlıda çalışıyor | ⏳ | | **Faz 4** | Dönem açma + aylık planlama + notlar | Plan ekranı | ⏳ | @@ -22,4 +22,11 @@ - ✅ 2. `.vscode/` yapılandırması ve monorepo iskeleti - ✅ 3. Hafıza dosyaları (`CLAUDE.md`, `MEMORY.md`, `PHASES.md`) + Git remote - ✅ 4. Yerel MongoDB (Docker, replica set) + `npm run dev` -- ⏳ 5. Veri modeli taslağı (`docs/DATA_MODEL.md`) → onay +- ✅ 5. Veri modeli taslağı (`docs/DATA_MODEL.md`) → onaylandı + +## Faz 1 Adımları +- 🔄 1. `config.rs` + `db.rs` (MongoDB bağlantısı), Türkçe hata yapısı, health check'e DB durumu +- ⏳ 2. Modeller, migration mekanizması ve indeksler +- ⏳ 3. Kimlik doğrulama: kayıt (+ hane + örnek kategori/ödeme türü), giriş, çıkış, `me`; argon2 + JWT cookie + rate limit +- ⏳ 4. Hane izolasyonu (middleware), üye davet kodu +- ⏳ 5. Kategori ve ödeme türü uç noktaları + hane izolasyonu testleri, `api.http`