Files
kakeibo/backend/src/services/stats.rs
T
suphicakir 30136ed1d3 feat(panel): günlük harcama ve dönem istatistikleri uç noktaları
- GET /periods/{id}/daily: dönemin her günü için 4 grubun harcaması
- GET /stats/periods: son dönemlerin harcanabilir/harcanan tutarı + toplam birikim
- Faz 7 adım planı

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

89 lines
2.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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<Vec<DaySpent>> {
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<PeriodStat>,
}
pub async fn periods(db: &Database, household_id: Uuid, limit: usize) -> AppResult<Stats> {
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,
})
}