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>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
//! 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): harcanabilir, harcanan, 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<AppState>,
|
||||
auth: AuthUser,
|
||||
Path(id): Path<String>,
|
||||
) -> AppResult<Json<Vec<DayDto>>> {
|
||||
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<usize>,
|
||||
}
|
||||
|
||||
#[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<PeriodStatDto>,
|
||||
}
|
||||
|
||||
#[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")]
|
||||
spendable: i64,
|
||||
#[ts(type = "number")]
|
||||
spent: i64,
|
||||
/// Kapanmamışsa `null`
|
||||
#[ts(type = "number | null")]
|
||||
to_savings: Option<i64>,
|
||||
}
|
||||
|
||||
pub async fn periods(
|
||||
State(s): State<AppState>,
|
||||
auth: AuthUser,
|
||||
Query(q): Query<StatsQuery>,
|
||||
) -> AppResult<Json<StatsDto>> {
|
||||
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 {
|
||||
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,
|
||||
spendable: x.spendable,
|
||||
spent: x.spent,
|
||||
to_savings: x.period.to_savings,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}))
|
||||
}
|
||||
Reference in New Issue
Block a user