87409590a6
- GET /reports/breakdown: grup, tüm kategoriler, ödeme türü ve ay kırılımı; dönem/tarih/grup/kategori/ödeme türü filtreleri - /stats/periods: gelir, sabit gider, devirler, gerçekleşen birikim - Faz 9 planı kullanıcı kararlarıyla güncellendi (ekstre içe aktarma, JSON geri dönüş noktası, aylık rapor) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
136 lines
4.0 KiB
Rust
136 lines
4.0 KiB
Rust
//! 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<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")]
|
||
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<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 {
|
||
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(),
|
||
}))
|
||
}
|