Files
kakeibo/docs/DATA_MODEL.md
T
2026-11-11 23:05:26 +03:00

9.8 KiB
Raw Blame History

🗄️ 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

{ _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 settings koleksiyonu 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 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

{ _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

{ _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)

  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.