//! Panel (Dashboard) verileri (KAKEIBO_PROMPT §8.2). Hiçbiri saklanmaz; her istekte toplanır. //! //! - `daily`: dönemin her günü için 4 grubun harcaması (harcama olmayan gün 0). //! Günlük/haftalık ve kümülatif seriler istemcide bundan türetilir. //! - `periods`: son N dönemin harcanabilir/harcanan tutarı (aylık grafik) ve //! **toplam birikim** = kapanışlarda birikime aktarılanların toplamı //! (varlık değerleri Faz 8'de bunun yerini alacak). Yeniden açılan dönemin son //! kapanıştaki tutarı, yeniden kapatılana kadar sayılır. use mongodb::{Database, bson::Uuid}; use crate::{ error::AppResult, models::period::Period, services::{ budget::{self, GroupAmounts}, calendar::{format_date, parse_date}, period, }, }; /// Aylık grafikte en fazla dönem sayısı pub const MAX_PERIODS: usize = 24; pub struct DaySpent { /// `"YYYY-MM-DD"` pub date: String, /// `Group::ALL` sırasıyla pub spent: GroupAmounts, } pub async fn daily(db: &Database, household_id: Uuid, period_id: Uuid) -> AppResult> { let p = period::get(db, household_id, period_id).await?; // Veritabanındaki tarihler her zaman doğrulanmış biçimde yazılır let start = parse_date(&p.start_date).expect("geçerli başlangıç tarihi"); let end = parse_date(&p.end_date).expect("geçerli bitiş tarihi"); let mut days = Vec::new(); let mut d = start; while d < end { days.push(DaySpent { date: format_date(d), spent: [0; 4], }); d += time::Duration::days(1); } for (group, date, amount) in budget::spent_by_group_and_date(db, household_id, period_id).await? { // Dönem tarihleri sonradan daraltıldıysa dışarıda kalan kayıt grafiğe girmez if let Some(day) = days.iter_mut().find(|x| x.date == date) { day.spent[group.order()] += amount; } } Ok(days) } pub struct PeriodStat { pub period: Period, pub spendable: i64, pub spent: i64, } pub struct Stats { /// Kapanışlarda birikime aktarılanların toplamı (tüm dönemler) pub total_saved: i64, /// Son `limit` dönem, **eskiden yeniye** pub periods: Vec, } pub async fn periods(db: &Database, household_id: Uuid, limit: usize) -> AppResult { let list = period::list(db, household_id).await?; // en yeni başta let total_saved = list.iter().filter_map(|p| p.to_savings).sum(); let mut out = Vec::new(); for p in list.into_iter().take(limit.clamp(1, MAX_PERIODS)) { let s = budget::summary(db, household_id, p.id).await?; out.push(PeriodStat { period: p, spendable: s.spendable, spent: s.spent_total, }); } out.reverse(); Ok(Stats { total_saved, periods: out, }) }