306c550b28
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.8 KiB
9.8 KiB
🗄️ 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
i128ile 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_atvb.): 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
{ _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
{ _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
settingskoleksiyonu yerine haneye gömülü (her hanede tek kayıt olduğu için ayrı koleksiyona gerek yok).
3.3 invites
{ _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
{ _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
opendönem (servis kontrolü + kısmi benzersiz indeks). - Yeniden açılan dönemin durumu tekrar
openolur. 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
{ _id, household_id, group: "needs", name: "Market", emoji: "🛒",
sort_order: 10, is_active: true, created_at, ... }
- Silinmez,
is_active: falseile 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
{ _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
{ _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
{ _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)
{ _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
{ _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
{ _id, household_id, period_id,
context: "planning" | "review",
title, body_md, created_by, created_at, ... }
3.12 assets — Varlıklar
{ _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
{ _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
{ _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
{ _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)
periods.status = "closed",closed_at,closed_bycarryoverskaydı (borç veya artı) → sonraki dönem- (Onaylıysa) gerçekleşen birikim →
asset_transactions(TRY,buy,source_period_id) - (İstenirse) yeni dönem:
plan_itemsve aylıkbudgetskopyalanarak açılır
Transaction'ın bir adımı hata verirse önceki adımlar da geri alınır, hiçbir şey değişmez.