Files
kakeibo/docs/DATA_MODEL.md
T
suphicakir 48554e0ff6 feat(birikim): dönem kapanışındaki birikimi varlıklara ekleme
- Kapatma sonucunda onayla "Birikime ekle" (TL alımı, dönemin son günü)
- Birikimler'de "Bekleyen birikimler" listesi
- Yeniden kapatmada bağlı kayıt yeni tutara güncellenir (0 → silinir), TL çıkışları aşılırsa reddedilir
- Panel "Toplam birikim" artık portföyün güncel değeri

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 01:27:07 +03:00

12 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, household_id /* = _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 }
  • household_id = _id (ortak scope() filtresi tüm koleksiyonlarda aynı çalışsın diye).
  • 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" | "correcting",
  savings_goal: 500000,                    // kuruş — birikim hedefi
  reflection_md: "...",                    // "Nasıl daha iyi yapabilirim?"
  to_savings: 450000,                      // kapanışta birikime aktarılan (Faz 8'de varlık hareketi)
  closed_at, closed_by, reopened_at, reopened_by,
  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çma (karar 2026-11-12, "düzeltme modu"): sahip kapalı dönemi açınca durum correcting olur; yazılabilir ama "açık dönem" sayılmaz → yeni dönem açık kalabilir (tek open kuralı korunur). Mevcut devir "yeniden kapat"a kadar yerinde kalır; yeniden kapatınca eskisi soft delete, yenisi hesaplanır. Ardından kapanmış dönem varsa açılamaz (zincirleme devirler bozulmasın).
  • 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. Sonraki dönem henüz yoksa to_period_id: null; o dönem açılınca (aynı transaction'da) bağlanır.
  • Yeniden kapatmada eskisi soft delete edilir, yenisi yazılır (düzeltme süresince eskisi geçerli kalı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",
  sort_order, is_active, created_at, ... }
  • Varsayılanlar hane açılınca gelir (migration v2 eski hanelere ekler): TRY, XAU_GRAM, XAU_CEYREK, XAU_YARIM, XAU_TAM, XAU_ONS, USD, EUR. Kullanıcı yalnızca döviz (fx, 3 harfli kod) ekler; kod hanede tekil. Pasif varlık listede kalır, yeni işlem almaz.

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: ağırlıklı ortalama maliyet (satışta çıkan maliyet = maliyet × satılan / eldeki; gerçekleşen kâr/zarar = satış tutarı − çıkan maliyet). Maliyet total'dan hesaplanır (birim fiyat bilgi amaçlı; verilmezse total ÷ quantity).
  • Tarih sırasıyla (aynı gün: ekleniş sırası) hiçbir anda eldeki miktar eksiye düşemez; ekleme, düzenleme ve silmede yeniden denetlenir.
  • TL varlığında quantity_e6 = total × 10.000, unit_price_e6 = 1.000.000.
  • Kapanıştan birikim: kullanıcı onaylayınca (POST /periods/{id}/to-savings) dönemin to_savings tutarı TL varlığına buy olarak eklenir: tarih = dönemin son günü, source_period_id = dönem; dönem başına tek. Eklenmemiş kapalı dönemler GET /savings/pending. Yeniden kapatmada bağlı kayıt yeni tutar ve tarihe güncellenir (tutar 0 → silinir), kapatma transaction'ı içinde; TL'den sonraki çıkışlar yeni tutarı aşacaksa kapatma reddedilir (kullanıcı kararı "a", 2026-11-13). Bağlı kayıt yoksa otomatik eklenmez (onay gerekir).

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: kod başına en yeni rate_date; aynı gün hem otomatik hem manuel varsa manuel. (Eski bir manuel kur, daha yeni otomatik kurun önüne geçmez.) Kur = 1 birimin TL karşılığı × 1e6; döviz alış kuru (değerleme).
  • Otomatik kurlar gün başına tek kayıt (kod × kaynak × gün; aynı gün yeniden çekilince güncellenir) → günlük geçmiş birikir. TCMB rate_date = bülten tarihi; altın = Türkiye'de bugün.
  • Altın: xaus.com gram (saf) USD × TCMB USD alış; çeyrek/yarım/tam = has gram (1,603 / 3,206 / 6,412) × gram, ons = 31,1034768 gr. İşçilik payı yok → gerekirse manuel.

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. Gerçekleşen birikimin to_savings kadarı birikime: şimdilik periods.to_savings; Faz 8'de → 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.