Files
kakeibo/docs/MEMORY.md
T
suphicakir 861bfba85a feat(değerlendirme): geçmiş dönemler ve düzeltme için yeniden açma
- Defter, Plan ve Değerlendirme'de dönem seçici (?donem=)
- Kapalı dönem salt okunur; düzeltmedeki dönem düzenlenebilir ve yeniden kapatılır
- Hane sahibi için onaylı "Düzeltme için yeniden aç"
- Geçmiş dönemler listesi, dönem durum rozeti

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 23:11:04 +03:00

28 KiB
Raw Blame History

🧠 Proje Hafızası

▶️ Sıradaki Adım

Faz 7 – Adım 1: Dashboard planı — önce KAKEIBO_PROMPT.md Dashboard bölümünü oku, adımları docs/PHASES.md'ye yaz ve kullanıcıya onaylat (özet kartları, grafikler; Recharts). 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

  • Yerel demo hesapları (kakeibo_dev): demo@ornek.com / demo12345 (owner, "Demo Hanesi") · es@ornek.com / demo12345 (member, aynı hane). npm run db:reset silerse API ile yeniden oluştur (ileride db:seed).
  • Sürüm kontrolü: Gitea (https://gitea.ta1sph.info/suphicakir/kakeibo.git), dal main.
  • Her adım sonunda Claude commit atar ve haber verir; push'u kullanıcı kendi uygulamasıyla yapar (Claude git push çalıştırmaz).
  • Node v26 (LTS) sistemde mevcut; ayrı kurulum gerekmedi.
  • 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.
  • Model yaklaşımı: #[serde(flatten)] yerine ortak alanlar models/common.rs yardımcılarıyla (new_meta, scope, touch). Modeller ilgili adımda eklenir.
  • households.household_id = _id (scope filtresi her yerde aynı).
  • Auth: JWT (HS256, jsonwebtoken rust_crypto) kakeibo_session cookie'de; HttpOnly, SameSite=Lax, Secure yalnızca APP_URL https ise. Beni hatırla → 30 gün Max-Age; değilse 12 saat JWT + oturum cookie'si. Kayıttan sonra 30 gün.
  • Giriş sınırı: e-posta başına 15 dk'da 5 hatalı deneme (bellek içi, auth/limiter.rs). Olmayan kullanıcıda da sahte argon2 doğrulaması (zamanlama sızıntısı yok).
  • Hata kodları: {error:{code,message}}; invalid_credentials, unauthorized, bad_request, conflict, too_many_requests.
  • Hane izolasyonu kuralı: hane kimliği her zaman AuthUser.household_id'den alınır, asla URL/gövdeden. Başka hanenin kaydı → 404 (403 değil; varlığı sızdırılmaz).
  • AuthUser her istekte DB'den kullanıcıyı doğrular (silinen kullanıcı → 401, rol DB'den). Owner-only işlemler: auth.require_owner()?.
  • Davet: 8 karakter (31 harflik okunaklı alfabe), 7 gün, tek kullanım; find_one_and_update ile atomik claim, kayıt transaction'ı içinde.
  • Kategoriler/ödeme türleri: tüm üyeler yönetir; silme yok (pasife alma); grup sonradan değişmez; ad grup içinde benzersiz (büyük/küçük harf duyarsız, pasifler dahil); PATCH'te version → iyimser kilit (409); sıralama tam liste ister.
  • Tasarım: ana renk indigo (oklch(0.42 0.13 268)), grup renkleri Okabe-Ito (needs #0072b2, wants #cc79a7, culture #009e73, unexpected #d55e00), *-fg tonları AA (açık ≥5.0, koyu ≥9) hesaplanarak doğrulandı. Gelir #15803d, gider #c62828, uyarı #b45309.
  • Tema seçimi localStorage (kakeibo-theme), yalnızca cihaz kolaylığı; ileride users.preferences.theme ile senkron olabilir.
  • parseMoney: virgül varsa ondalık; virgül yoksa son noktadan sonra 1–2 rakam → ondalık ("1.25" = 1,25 ₺; "1.250" = 1250 ₺).
  • Logo: Taslak A (indigo zemin, spiral krem defter, ¥) seçildi. Kaynak frontend/brand/logo.svg + logo-fullbleed.svg (maskable/Apple, içerik %82). Uygulama içi components/logo.tsx aynı çizim (elle senkron tutulmalı). Manifest Faz 10'da (vite-plugin-pwa).
  • ts-rs 12: backend/.cargo/config.toml → TS_RS_EXPORT_DIR=../frontend/src/types. TS adları: User, HouseholdSummary, Household, Member, Invite, CatalogItem, Catalog{Create,Update,Order}Request, RegisterRequest, LoginRequest, MeResponse, HealthResponse, ApiErrorBody, Group, Role.
  • QueryClient: 4xx tekrar denenmez; herhangi bir sorgu 401 alırsa me = null. Çıkışta qc.clear().
  • Adresler Türkçe: / (Panel), /defter, /plan, /birikim, /diger (mobil), /degerlendirme, /raporlar, /ayarlar, /giris, /kayit. Mobil alt menü: Panel, Defter, Plan, Birikim, Diğer.
  • Çıkış: qc.clear() KULLANMA (açık ekranlar silinen sorguya bağlı kalıyor) → önce me=null, sonra removeQueries (me hariç).
  • Canlıda SPA yönlendirmesi (Axum: bilinmeyen yol → index.html) Faz 3'te yapılmalı.
  • Kayıt ekranı: "Yeni hane kur" / "Davetle katıl"; davet bağlantısı /kayit?davet=KOD. Sunucu 409 → e-posta alanı, "Davet…" 400 → kod alanı.
  • Yönlendirme sonrası toast için mutateAsync + await kullanıldı (mutate() callback'leri bileşen kalkınca çalışmayabilir).
  • Kayıt politikası (kullanıcı kararı, 2026-11-12): Seçenek 1 → REGISTRATION=invite_only varsayılan; sistemde hiç hane yokken ilk kayıt yeni hane kurar (sunucu sahibi), sonra yalnızca davetle. open isteğe bağlı. Canlıda ilk hesabı kullanıcı /kayit'tan açar; demo hesaplar canlıya taşınmaz.
  • Prettier ayarı eklendi (önceden yoktu → VS Code kaydedince çift tırnağa çevirebilirdi).
  • Canlı imaj: node:26-alpine → rust:1.99-slim-trixie → debian:trixie-slim (ca-certificates + curl, uid 10001 kakeibo), 174 MB. Docker katman önbelleği için sahte lib/main ile bağımlılık derlemesi; kalıntılar *kakeibo* + .fingerprint silinir, gerçek kaynak touchlanır (yoksa sahte lib kullanılıyordu).
  • Önbellek: HTML no-cache, /assets/* 1 yıl immutable, diğer statik 1 saat. Bilinmeyen /api/* → JSON 404.
  • Canlı: domain kakeibo.ta1sph.website (DNS → 159.146.61.151, ev dış IP'si olmalı), Gitea Dokploy'a bağlı. Canlı MongoDB = ayrı Dokploy servisi kakeibo-kakeibomongo-fibzhc (rs0), kullanıcı kakeibo_app (readWrite/kakeibo).
  • Dönem: bitiş hariç, en fazla 62 gün, hane başına tek açık, çakışma yok (metin karşılaştırması), kapalı salt okunur, daraltmada dışarıda harcama kalırsa 409. Ad boşsa otomatik (calendar::default_period_name). Tüm üyeler açabilir/düzenleyebilir.
  • ts-rs: i64 tutarlar #[ts(type = "number")]; isteklerde Option<i64> için #[ts(type = "number", optional)] (yoksa ? kayboluyor).
  • Plan kalemleri: tarih isteğe bağlı (dönem içinde), tutar > 0, kopyalama göreli gün + dönem sonuna kırpma, aynı tür+ad (büyük/küçük harf duyarsız) atlanır. Notlar: başlık 1–120, metin ≤ 20.000. Kapalı dönemde hepsi 409 (period::get_open). Silme = soft delete.
  • İzolasyon iki katmanlı: kayıt okuma scope() + dönem get_open(hid, period_id) + yazma filtresi scope(). Tek katmanı bozmak açık oluşturmuyor (mutasyon denemesiyle görüldü).
  • Bütçe: aylık = budgets{scope:"month"}; haftalık otomatik = (aylık − elle girilenler) gün oranında, money::split_proportional (en büyük kalan, toplam tam eşit). Elle hafta = scope:"week", week_start; null → soft delete. Aylık değişince elle haftalar korunur; elle toplam > aylık → weekly_over.
  • Özet: spendable = gelir − sabit gider − birikim − borç + artı (carryovers.to_period_id). unallocated = spendable − bütçe toplamı (negatif = fazla dağıtım).
  • Öneri: default = hane settings.budget_split_pct × spendable (≤0 → available:false); previous/avg3 = önceki kapalı dönem(ler)in entries toplamı / dönem sayısı (yuvarlayarak).
  • Sürümler: Rust 1.99.1 (edition 2024), Axum 0.8, React 19, Vite 8, TypeScript 7, Tailwind 4.3, shadcn 4.23, Vitest 5, react-router 8, TanStack Query 5.

❓ Açık Konular

  • Geliştirme makinesi: CachyOS nodejs 26.13.0-2 ↔ simdjson .so.36 uyumsuzluğu → simdjson 2:5.0.5-1'e düşürüldü (2026-11-12). Düzeltilmiş nodejs gelene kadar pacman -Syu --ignore simdjson. node: libsimdjson.so.34 hatası görülürse sebep budur.
  • 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ç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.

📝 İlerleme Günlüğü

2026-11-11 — Faz 0

  • Adım 1: Ortam kontrol edildi. CachyOS, fish. Node 26, npm 12, Docker 29 + Compose 5, Git, VS Code, gcc mevcut. Rust ve cargo-watch eksik; kurulum komutları verildi.
  • 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ı.

2026-11-11 — Faz 1

  • Adım 1: config.rs (env + doğrulama, JWT_SECRET ≥ 32), db.rs (5 sn timeout + ping), error.rs (AppError → {error:{code,message}} Türkçe; 5xx detayı yalnızca logda), state.rs, routes/ (health: DB yoksa 503). Açılışta DB/ayar hatasında anlaşılır log + çıkış kodu 1. Elle test: 4 senaryo geçti.
  • Adım 2: db/ modülü: col sabitleri, migrations.rs (schema_migrations, v1 = koleksiyonları oluştur), indexes.rs (DATA_MODEL §4 + tüm hane koleksiyonlarına (household_id, updated_at)). models/common.rs: UUID v7, new_meta, scope, touch. Açılışta db::prepare. 4 birim testi; idempotency ve tek açık dönem indeksi elle doğrulandı. uuid (v7) eklendi.
  • Adım 3: Kimlik doğrulama: auth/ (password argon2id + spawn_blocking, jwt, limiter, session cookie + AuthUser extractor), extract.rs (ValidJson = JSON + validator, Türkçe 400), services/account.rs (kayıt tek transaction: hane + owner + 13 kategori + 4 ödeme türü), services/seed.rs, routes/auth.rs (register/login/logout/me). Yeni crate'ler: argon2, jsonwebtoken, axum-extra(cookie), validator, time. 11 birim testi; 13 senaryo curl ile, rollback DB'de doğrulandı.
  • Adım 4: AuthUser DB doğrulaması + require_owner, services/invite.rs (create/list_active/revoke/claim), kayıt davet koduyla member olarak katılma (services/account.rs yeniden düzenlendi), routes/household.rs (GET /household, invites GET/POST/DELETE). 13 birim testi; 15 senaryo e2e (izolasyon 404, member 403, kod tekrar kullanımı, rollback) geçti.
  • Adım 5: models/group.rs, services/catalog.rs (kategori + ödeme türü ortak: list/create/update/reorder), routes/catalog.rs. Backend lib+bin'e ayrıldı (AppState::new). Entegrasyon testleri: tests/isolation.rs (3), tests/catalog.rs (4) — toplam 22 test. İzolasyon testi, kasıtlı açık eklenerek doğrulandı (yakaladı). db:reset artık test DB'lerini de siler. Dev-deps: tower, http-body-util. Faz 1 tamamlandı.

2026-11-12 — Faz 2

  • Adım 1: Tailwind v4 (@tailwindcss/vite), shadcn init (radix, preset nova; cn paketi = shadcn'in clsx+tailwind-merge yerine geçeni, incelendi), Inter (@fontsource-variable, çevrimdışı). index.css: grup + durum token'ları, odak halkası, money utility, reduced-motion. lib/format.ts (+22 vitest), lib/theme.tsx, lib/groups.ts, components/theme-toggle.tsx. Butonlar 44/48 px'e büyütüldü. Geçici önizleme ekranı (App.tsx), 375 px headless ekran görüntüsüyle iki temada kontrol edildi.
  • Adım 2: 3 logo taslağı (A indigo defter, B açık defter + para, C hanko) 512/64/32/16 px önizlemeyle sunuldu; kullanıcı A'yı seçti. scripts/icons.sh (npm run icons): favicon.svg/.ico, icon-192/512, icon-maskable-512, apple-touch-icon (180), brand/icon-1024 (Tauri). Maskable güvenli alan görsel olarak doğrulandı. index.html: ikon linkleri, theme-color, description.
  • Adım 3: ts-rs eklendi (17 tip), health ve hata gövdesi struct'a çevrildi (ApiErrorBody). Frontend: @tanstack/react-query, lib/api.ts (+7 vitest), lib/query.ts, lib/auth.ts, main.tsx'te QueryClientProvider. Geçici oturum paneli. Tarayıcı e2e (CDP betiği): yanlış şifre mesajı, giriş, yenilemede kalıcılık, httpOnly (document.cookie'de yok), çıkış — hepsi geçti.
  • Adım 4: react-router 8 (createBrowserRouter), RequireAuth/RequireGuest/Splash, AppLayout (masaüstü kenar çubuğu + mobil üst çubuk + 5'li alt menü, safe-area), Diğer sayfası, 7 yer tutucu sayfa, 404, lib/strings.ts, useDocumentTitle, lib/return-path.ts (+10 test, açık yönlendirme koruması), ThemeToggle compact. Tasarım önizlemesi → /dev/tasarim (yalnızca DEV). CDP e2e (10 kontrol) iki hata buldu, düzeltildi: giriş sonrası donus yarışı, çıkışta qc.clear(). Masaüstü içerik boşluğu düzeltildi.
  • Adım 5: react-hook-form, zod 4, @hookform/resolvers, sonner; shadcn input/label/checkbox/card (44 px / 20 px'e büyütüldü). components/form-field.tsx, components/toaster.tsx, features/auth/ (auth-shell, login-page, register-page, schemas + 8 vitest). Metinler t.auth. CDP e2e 11 kontrol geçti (boş form, yanlış şifre, giriş + toast, davet linki, kayıtlı e-posta → alan hatası, geçersiz davet → alan hatası, yeni hane). Frontend 47 test. Faz 2 tamamlandı.

2026-11-12 — Faz 3 öncesi

  • Kullanıcı api.http'deki kayıt örneğini (yeni@ornek.com) hesap sandı → dosya başlığına hazır hesaplar kutusu ve "bu bir KAYIT isteğidir" uyarısı eklendi.
  • Kayıt politikası: config.rs RegistrationMode, AppError::RegistrationClosed (403), account::new_household_allowed, GET /api/auth/registration (RegistrationInfo TS tipi). tests/registration.rs (2). Kayıt ekranı kapalıyken yalnızca davet modu + açıklama. .env/.env.example: REGISTRATION=invite_only. Prettier (.prettierrc.json, .prettierignore, format / format:check), tüm src biçimlendirildi (yalnızca boşluk).

2026-11-12 — Faz 3

  • Adım 1: tower-http (fs, compression gzip/br, trace). routes::app() + routes/spa.rs (ServeDir + index.html fallback + cache middleware), API fallback JSON 404, STATIC_DIR config, graceful shutdown (SIGTERM/Ctrl+C). Dockerfile, .dockerignore, docs/DEPLOY.md başlatıldı. İmaj yerelde çalıştırıldı (host network, 8090): 10 kontrol + tarayıcı ekran görüntüsü geçti. İlk derlemede Docker önbellek hilesi hatası (sahte lib.rs) bulundu, düzeltildi.
  • Adım 2: Mevcut tek MongoDB (butcem-mongo) Bütçem'e ait → kullanıcı ayrı MongoDB seçti. Dokploy MongoDB (replica set) kuruldu, kakeibo_app oluşturuldu, port yayını yok (iki servis de null). Sorunlar: kullanıcı şifreyi komuta düz yazıp paylaştı; Dokploy betiği root şifresini servis Args'ına düz yazıyor → iki şifre de mongosh içinde üretilerek yenilendi. passwordPrompt yapıştırma hatası (U_STRINGPREP). Auth açma denemesi: Dokploy özel Command'ı uygulamadı → auth kapalı kabul edildi (açık konu).
  • Adım 3–4: Dokploy Application kakeibo-app (iç ad kakeibo-kakeiboapp-nd5kek). Sorunlar: push yapılmamıştı (Dockerfile yok); Provider GitHub sekmesinde kalmıştı (Github Provider not found, kullanıcı çözdü); Traefik 404 (kullanıcı çözdü, büyük olasılıkla domain sonrası redeploy). Yayında: https://kakeibo.ta1sph.website — health ok, Let's Encrypt, ilk hesap açıldı. Faz 3 tamamlandı (yedekleme hariç).

2026-11-12 — Faz 4

  • Adım 1: services/calendar.rs (parse/format, weeks Pazartesi başlangıçlı + kısmi haftalar, Türkçe ay adları; 4 test), models/period.rs, services/period.rs (2 test), routes/period.rs (list/current/create/get/patch; DTO'da last_date, days, weeks). tests/periods.rs (5) — çakışma kontrolü bilerek bozulunca test yakaladı. Backend toplam 58 test.
  • Adım 2: models/plan_item.rs, models/note.rs, services/plan.rs (list/create/update/delete/copy_previous), services/note.rs, routes/plan.rs, routes/note.rs, period::get_open + closed(). tests/plan_notes.rs (5): CRUD + doğrulama, kopyalama (göreli gün, 28 Şubat'a kırpma, tekrar yok), kapalı dönem salt okunur, not filtresi, izolasyon. Backend 72 test.
  • Adım 3: services/money.rs (split_proportional + 3 test; beklenen değer hatası testteydi, elle hesapla düzeltildi), services/budget.rs (summary, view, set_monthly, set_week, suggest), routes/budget.rs (5 uç nokta, 9 TS tipi). tests/budget.rs (6) — özet formülü bilerek bozulunca yakaladı. Backend 88 test.
  • Adım 4: İlk dönem sihirbazı /plan/donem-ac?adim=1..5 (features/period-wizard/): Dönem → Gelirler → Giderler → Birikim → Bütçe. Dönem 1. adımda açılır, her adım hemen kaydedilir; adım adreste (yenileme/geri tuşu çalışır). lib/periods.ts (Query kancaları; değişince queryKeys.period(id) öneki yenilenir), lib/period-dates.ts (son gün dahil ↔ backend bitiş hariç), lib/money-schema.ts (moneyText, toKurus), components/money-input.tsx (inputMode=decimal, blur'da 1.250,50), PageLoading/PageError, SummaryTiles, NoPeriodCard (Panel + Plan). Frontend 60 test; CDP e2e 43 kontrol geçti, test verisi demo hanesinden silindi.
  • Not: üretim JS paketi ~620 kB (gzip 195 kB) → Faz 10'da sayfa bazlı bölme (lazy route).
  • Adım 5: Plan ekranı (features/planning/): plan-read.tsx (salt okunur: kalemler, 4 grup + haftalık döküm, "elle" etiketi), plan-edit.tsx (taslak formu, Kaydet/Vazgeç, useBlocker + beforeunload ile kaydedilmemiş değişiklik onayı). lib/plan-draft.ts (toDraft/diffPlan: yalnızca değişenler gönderilir), lib/budget-preview.ts (backend split_proportional + view ile birebir; aynı test örnekleri), useSavePlan (sıralı istekler; hata olursa yeniden yükler, transaction değil). Ortak: DistributionBar, SuggestionPicker (sihirbaz da kullanıyor). Frontend 69 test.
  • E2E artık ayrı sunucuda: REGISTRATION=open PORT=8091 STATIC_DIR=frontend/dist MONGODB_URI=…/kakeibo_test_e2e (kullanıcının dev verisine dokunulmaz; bitince DB silinir). 31 kontrol geçti; ekran görüntüsüyle 3 düzen sorunu bulundu/düzeltildi (dar satırlar, mobil tutar/tarih, hafta etiketi).
  • Adım 6: Notlar: features/notes/ (NotesSection — Plan'da context="planning", Değerlendirme'de review ile yeniden kullanılacak; NoteEditor Yaz/Önizle sekmeleri; zod şeması karakteri backend gibi sayar), lib/notes.ts (+ useHousehold yazar adı için), components/markdown.tsx (ham HTML yok, bağlantılar yeni sekme), components/ui/textarea.tsx, index.css .markdown stilleri. Yeni bağımlılık: react-markdown 10, remark-gfm 4. E2E 16 kontrol (XSS, 409 çakışma dahil). DistributionBar 0/0 durumunda "tamamı dağıtıldı" demiyor. Faz 4 tamamlandı.
  • Not: JS paketi 820 kB (gzip 254 kB; markdown ~200 kB) → Faz 10'da route bazlı lazy + Markdown'u ayrı parçaya al. npm audit uyarıları shadcn CLI bağımlılıklarından (deepmerge, minimatch), uygulamaya girmiyor; --force shadcn'i 1.0'a düşürür, yapılmadı.

2026-11-12 — Faz 5

  • Adım 1: Harcamalar backend: models/entry.rs, services/entry.rs (list + from/to filtresi, sıra date ↓ _id ↓; create/update/delete soft/restore), routes/entry.rs (5 uç nokta), catalog::find. Grup kategoriden türetilir (istemci göndermez). Pasif kategori/ödeme türü yeni seçimde reddedilir, kaydın mevcut değeri olarak kalabilir. Ödeme türü isteğe bağlı; düzenlemede "" → kaldır (projedeki "boş metin = kaldır" kuralı; ts-rs deserialize_with desteklemediği için çift-Option yerine). tests/entries.rs (6) — previous önerisi artık gerçek kayıtla test edildi. Mutasyon: grup türetme bozulunca yakalandı; restore'da hane filtresi tek başına kaldırılınca get_open ikinci katman olarak korudu, ikisi birden kaldırılınca 2 test yakaladı. Backend 97 test.
  • Adım 2: Gerçekleşen harcama (saklanmaz, her istekte aggregation): GET /summary → spent_total, remaining (= harcanabilir − harcanan, negatifse aşılmış). GET /budget → grupta spent (dönem), haftada spent. spent_by_group_and_date (grup×gün toplanır, Rust'ta haftalara dağıtılır; tarih metin karşılaştırması). Silinen kayıt sayılmaz, kategori değişince harcama yeni gruba geçer. Test actual_spending_in_summary_and_budget (hafta sınırları 6/7 Aralık, dönem son günü, önceki dönem hariç, geri al). Mutasyon: hafta sınırı <=→< ve silinenleri sayma → ikisi de yakalandı. Backend 98 test.
  • Adım 3: Defter ekranı (features/ledger/): WeekPicker (◀ ▶, "Bu hafta", haftanın harcanan/bütçe toplamı), EntryForm (büyük MoneyInput, 4 renkli grup düğmesi, kategori/ödeme türü çipleri, tarih, açıklama; kayıttan sonra tutar+açıklama temizlenir, odak tutara döner), schema.ts (entrySchema(start,last), fieldForError: sunucu mesajı → alan). Hafta adreste: /defter?hafta=N (bugünün haftasında parametre yok); başka haftaya tarihli kayıt → o haftaya geçer; hafta değişince tarih o haftaya çekilir. lib/ledger.ts (weekIndexFor, defaultEntryDate, parseWeekParam, son seçim localStorage kakeibo.ledger.last: grup + her grubun son kategorisi + ödeme türü; pasifleşen seçim düşer). lib/catalog.ts (useCategories, usePaymentMethods, 5 dk staleTime), lib/entries.ts (useEntries, useCreateEntry), queryKeys.entries. Frontend 79 test; e2e 31 kontrol (ayrı 8091 sunucu, DB silindi). Mobilde "Beklenmeyen" taşması düzeltildi.
  • Adım 4: Defter listesi ve özet: entry-list.tsx (gün gün groupByDay, gün adı formatWeekday = Intl tr-TR UTC; satır: kategori emojisi grup renginde, grup · ödeme · açıklama; düzenle/sil 44 px), sil → sonner action "↩️ Geri al" (8 sn) → useRestoreEntry. Düzenleme aynı EntryForm (entry + key, Güncelle/Vazgeç; version gönderilir, 409 Türkçe root hatası; '' → ödeme/açıklama kaldır). Katalog pasifler dahil okunur (eski kayıtların adı; seçenekte yalnız aktifler + kaydın mevcut değeri). week-summary.tsx grup bazlı bütçe/harcanan/kalan + çubuk, aşımda "Aşıldı" kırmızı. Masaüstü iki sütun (form solda yapışkan); dar sütunda taşmaya karşı container query (@container, @lg:grid-cols-4). Frontend 81 test; e2e 29 kontrol (+ Adım 3'ün 31'i yeniden geçti). Not: e2e betikleri aynı DB'yi paylaşırsa birbirini bozar → her biri temiz DB ile.
  • Adım 5: Yedekleme betikleri: scripts/backup.sh (Swarm etiketiyle konteyneri bulur, mongodump --archive --gzip → .partial → gzip -t + mongorestore --dryRun → ad verilir; son KEEP=14, BACKUP_GROUP ile okuma izni), scripts/restore.sh (dosya adından kaynak DB, --nsFrom/--nsTo ile başka adla; mevcut hedefe yalnızca --force + --drop; COMPARE_DB ile sayım karşılaştırma), scripts/backup-pull.sh (rsync SSH BatchMode, ~/Yedekler/kakeibo, son 60, 48 saatten eski → notify-send). deploy/systemd/ (sunucu 03:30, bu bilgisayar 09:00 + açılış, Persistent=true). Kullanıcı kararı: sunucu dışı kopya bu bilgisayara (S3 değil). npm run db:backup / db:restore (yerel, backups/ gitignore). Yerel test: yedek → geri yükleme sayıları kakeibo_dev ile aynı, koruma, bozuk dosya hatası, saklama ✅.

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").
  • Adım 3: Değerlendirme sayfası (features/review/): Questions (4 kart; 1. kart hesap açıklaması, 3. bütçe yüzdesi, 4. hedefe göre durum lib/review.ts → savingsStatus: above/below/deficit), breakdowns.tsx (GroupComparison, TopCategories ilk 5 + "Tümünü göster", WeeklyDistribution; ortak Bar, ratio), Reflection (görüntüle/düzenle, useUpdatePeriod + version, 409 formda; şema features/review/schema.ts), NotesSection collapsed (yalnız başlık, aria-expanded ile açılır). Ortak components/markdown-tabs.tsx (Yaz/Önizle, useId kimlikler; NoteEditor da kullanıyor). useReview + queryKeys.review (dönem önekinde → kayıt değişince yenilenir). Dönem sürüyorsa bilgi satırı. Frontend 84 test; e2e 24 kontrol (Ekim kapatılıp Kasım'a artı devri dahil). Mobil haftalık satır sıkıştırıldı.
  • Adım 4: Kapatma sihirbazı /degerlendirme/kapat (features/review/close-page.tsx): Özet (4 soru, erken kapatma uyarısı, "Nasıl daha iyi" boşsa uyarı + bağlantı) → Birikim (artıda MoneyInput varsayılan tamamı, Tamamı/Hiçbiri, canlı önizleme; açıkta/sıfırda yalnız bilgi) → Onay (useClosePeriod, version; 409 formda, onSettled tüm ['period'] + liste yenilenir → ikinci deneme güncel sürümle) → Sonuç (sayfa durumunda; kapanınca current null). lib/review.ts → settlePreview (backend settle ile aynı tutulmalı), şema close-schema.ts (0..gerçekleşen). Stepper artık steps/label alıyor. Dönem açma sihirbazı tarih önerisi: suggestRanges(today, prevEnd) / rangeAfter — son dönemin bitişinden başlar, çakışan ay önerilmez (erken kapatılan ay tekrar önerilmezdi hatası önlendi). Not: moneyToText(0) → '' (plan taslağı için); 0 göstermek gerekiyorsa formatNumber. Frontend 89 test; e2e 33 kontrol (DB silindi, 8091 kapatıldı).
  • Adım 5: Dönem seçimi: lib/period-select.ts (useSelectedPeriod: ?donem= → listeden; yoksa açık dönem, o da yoksa düzeltmedeki; pickPeriod, isWritable, canReopen = backend kuralı, periodPath), features/periods/ (PeriodPicker başlıkta <select>, açık döneme dönünce parametre kalkar; SelectedPeriod yükleniyor/hata/bulunamadı/yok kapısı). Defter, Plan, Değerlendirme, kapatma sihirbazı buna geçti (Panel ve dönem sihirbazı hâlâ useCurrentPeriod). Defter kapalı dönemde salt okunur (form yok, EntryList readOnly); hafta değişimi donem'i korur. Plan: düzenleme isWritable, kopyalama yalnız açık. PeriodHeading durum rozeti. Değerlendirme sonu ClosingSection (açık → Kapat · düzeltmede → Defter'de/Plan'da düzelt + Yeniden Kapat · kapalı → kapanış bilgisi + sahip için satır içi onaylı "Düzeltme için yeniden aç") + PeriodHistory. useReopenPeriod; useUpdatePeriod liste önbelleğini de günceller (seçili dönem listeden → art arda kayıtta 409 olmasın). Ders: başarıda bileşen kalkıyorsa mutate(…, {onSuccess}) çalışmaz → mutateAsync kullan. Frontend 92 test; e2e 36 + kapatma 33 yeniden (DB silindi). Faz 6 ✅.