//! Panel (Dashboard) uç noktaları (hanenin tüm üyeleri): //! //! | Yöntem | Yol | İş | //! |---|---|---| //! | GET | `/api/periods/{id}/daily` | dönemin her günü: 4 grubun harcaması | //! | GET | `/api/stats/periods?limit=12` | son dönemler (eskiden yeniye): gelir, sabit gider, devirler, harcanabilir, harcanan, gerçekleşen birikim, birikime aktarılan + toplam birikim | use axum::{ Json, extract::{Path, Query, State}, }; use mongodb::bson::Uuid; use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::{ auth::AuthUser, error::{AppError, AppResult}, models::period::PeriodStatus, routes::budget::GroupAmounts, services::{ calendar::{format_date, parse_date}, stats, }, state::AppState, }; #[derive(Serialize, TS)] #[ts(export, rename = "DaySpent")] pub struct DayDto { /// `"YYYY-MM-DD"` date: String, /// O gün gruplara göre harcanan (kuruş) spent: GroupAmounts, } pub async fn daily( State(s): State, auth: AuthUser, Path(id): Path, ) -> AppResult>> { let id = Uuid::parse_str(&id).map_err(|_| AppError::NotFound)?; let days = stats::daily(&s.db, auth.household_id, id).await?; Ok(Json( days.into_iter() .map(|d| DayDto { date: d.date, spent: d.spent.into(), }) .collect(), )) } #[derive(Deserialize)] pub struct StatsQuery { limit: Option, } #[derive(Serialize, TS)] #[ts(export, rename = "PeriodStats")] pub struct StatsDto { /// Kapanışlarda birikime aktarılanların toplamı (kuruş) #[ts(type = "number")] total_saved: i64, /// Eskiden yeniye periods: Vec, } #[derive(Serialize, TS)] #[ts(export, rename = "PeriodStat")] struct PeriodStatDto { id: String, name: String, start_date: String, /// Son gün, dahil last_date: String, status: PeriodStatus, #[ts(type = "number")] income_total: i64, #[ts(type = "number")] fixed_expense_total: i64, #[ts(type = "number")] savings_goal: i64, #[ts(type = "number")] carryover_debt: i64, #[ts(type = "number")] carryover_surplus: i64, #[ts(type = "number")] spendable: i64, #[ts(type = "number")] spent: i64, /// Harcanan, gruplara göre spent_by_group: GroupAmounts, /// Gelir − sabit − borç + artı − harcanan (negatifse açık). Birikim oranı = bu ÷ gelir #[ts(type = "number")] realized_savings: i64, /// Kapanmamışsa `null` #[ts(type = "number | null")] to_savings: Option, } pub async fn periods( State(s): State, auth: AuthUser, Query(q): Query, ) -> AppResult> { let st = stats::periods(&s.db, auth.household_id, q.limit.unwrap_or(12)).await?; Ok(Json(StatsDto { total_saved: st.total_saved, periods: st .periods .into_iter() .map(|x| { let end = parse_date(&x.period.end_date).expect("geçerli bitiş tarihi"); PeriodStatDto { realized_savings: x.realized_savings(), id: x.period.id.to_string(), name: x.period.name, start_date: x.period.start_date, last_date: format_date(end - time::Duration::days(1)), status: x.period.status, income_total: x.summary.income_total, fixed_expense_total: x.summary.fixed_expense_total, savings_goal: x.summary.savings_goal, carryover_debt: x.summary.carryover_debt, carryover_surplus: x.summary.carryover_surplus, spendable: x.summary.spendable, spent: x.summary.spent_total, spent_by_group: x.summary.spent_by_group.into(), to_savings: x.period.to_savings, } }) .collect(), })) }