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>
184 lines
6.4 KiB
Rust
184 lines
6.4 KiB
Rust
//! Panel verileri: günlük harcama ve dönem istatistikleri (toplam birikim dahil).
|
||
|
||
mod common;
|
||
|
||
use axum::http::StatusCode;
|
||
use common::{Session, TestApp};
|
||
use serde_json::{Value, json};
|
||
|
||
async fn open(app: &TestApp, s: &Session, start: &str, end: &str, goal: i64) -> String {
|
||
let (st, p) = app
|
||
.post(
|
||
"/periods",
|
||
s,
|
||
json!({ "start_date": start, "end_date": end, "savings_goal": goal }),
|
||
)
|
||
.await;
|
||
assert_eq!(st, StatusCode::CREATED, "{p}");
|
||
p["id"].as_str().unwrap().to_string()
|
||
}
|
||
|
||
async fn category(app: &TestApp, s: &Session, group: &str) -> String {
|
||
let (_, cats) = app.get("/categories", s).await;
|
||
cats.as_array()
|
||
.unwrap()
|
||
.iter()
|
||
.find(|c| c["group"] == group)
|
||
.unwrap()["id"]
|
||
.as_str()
|
||
.unwrap()
|
||
.to_string()
|
||
}
|
||
|
||
async fn spend(app: &TestApp, s: &Session, pid: &str, date: &str, amount: i64, cat: &str) -> Value {
|
||
let (st, e) = app
|
||
.post(
|
||
&format!("/periods/{pid}/entries"),
|
||
s,
|
||
json!({ "date": date, "amount": amount, "category_id": cat }),
|
||
)
|
||
.await;
|
||
assert_eq!(st, StatusCode::CREATED, "{e}");
|
||
e
|
||
}
|
||
|
||
async fn income(app: &TestApp, s: &Session, pid: &str, amount: i64) {
|
||
let (st, b) = app
|
||
.post(
|
||
&format!("/periods/{pid}/plan-items"),
|
||
s,
|
||
json!({ "kind": "income", "name": "Maaş", "amount": amount }),
|
||
)
|
||
.await;
|
||
assert_eq!(st, StatusCode::CREATED, "{b}");
|
||
}
|
||
|
||
/// Kasım: gelir 10.000, harcama 3.000, 5.000 birikime → 2.000 artı Aralık'a.
|
||
/// Aralık: gelir 20.000, hedef 1.000 → harcanabilir 21.000.
|
||
async fn two_periods(app: &TestApp, s: &Session) -> (String, String) {
|
||
let needs = category(app, s, "needs").await;
|
||
let nov = open(app, s, "2026-11-01", "2026-12-01", 0).await;
|
||
income(app, s, &nov, 1_000_000).await;
|
||
spend(app, s, &nov, "2026-11-30", 300_000, &needs).await;
|
||
let (st, b) = app
|
||
.post(
|
||
&format!("/periods/{nov}/close"),
|
||
s,
|
||
json!({ "to_savings": 500_000 }),
|
||
)
|
||
.await;
|
||
assert_eq!(st, StatusCode::OK, "{b}");
|
||
let dec = open(app, s, "2026-12-01", "2027-01-01", 100_000).await;
|
||
income(app, s, &dec, 2_000_000).await;
|
||
(nov, dec)
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn daily_spending_by_group() {
|
||
let app = TestApp::new().await;
|
||
let s = app.register("Test", None).await;
|
||
let (_, dec) = two_periods(&app, &s).await;
|
||
let needs = category(&app, &s, "needs").await;
|
||
let wants = category(&app, &s, "wants").await;
|
||
spend(&app, &s, &dec, "2026-12-01", 100_000, &needs).await;
|
||
spend(&app, &s, &dec, "2026-12-01", 50_000, &wants).await;
|
||
spend(&app, &s, &dec, "2026-12-07", 20_000, &needs).await;
|
||
spend(&app, &s, &dec, "2026-12-31", 7_000, &needs).await;
|
||
let gone = spend(&app, &s, &dec, "2026-12-02", 99_000, &needs).await;
|
||
let (st, _) = app
|
||
.delete(&format!("/entries/{}", gone["id"].as_str().unwrap()), &s)
|
||
.await;
|
||
assert_eq!(st, StatusCode::NO_CONTENT);
|
||
|
||
let (st, days) = app.get(&format!("/periods/{dec}/daily"), &s).await;
|
||
assert_eq!(st, StatusCode::OK, "{days}");
|
||
let days = days.as_array().unwrap();
|
||
assert_eq!(days.len(), 31, "her gün, harcama olmasa da");
|
||
assert_eq!(days[0]["date"], "2026-12-01");
|
||
assert_eq!(
|
||
days[0]["spent"],
|
||
json!({ "needs": 100_000, "wants": 50_000, "culture": 0, "unexpected": 0 })
|
||
);
|
||
assert_eq!(days[1]["spent"]["needs"], 0, "silinen kayıt sayılmaz");
|
||
assert_eq!(days[6]["spent"]["needs"], 20_000);
|
||
assert_eq!(days[30]["date"], "2026-12-31");
|
||
assert_eq!(days[30]["spent"]["needs"], 7_000, "son gün dahil");
|
||
// Kasım'ın 30.11 harcaması Aralık'a karışmaz
|
||
let total: i64 = days
|
||
.iter()
|
||
.map(|d| d["spent"]["needs"].as_i64().unwrap())
|
||
.sum();
|
||
assert_eq!(total, 127_000);
|
||
|
||
// Başka hane göremez
|
||
let other = app.register("Diger", None).await;
|
||
let (st, _) = app.get(&format!("/periods/{dec}/daily"), &other).await;
|
||
assert_eq!(st, StatusCode::NOT_FOUND);
|
||
app.cleanup().await;
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn period_stats_and_total_savings() {
|
||
let app = TestApp::new().await;
|
||
let s = app.register("Test", None).await;
|
||
let (nov, dec) = two_periods(&app, &s).await;
|
||
let needs = category(&app, &s, "needs").await;
|
||
let culture = category(&app, &s, "culture").await;
|
||
spend(&app, &s, &dec, "2026-12-05", 40_000, &needs).await;
|
||
spend(&app, &s, &dec, "2026-12-06", 2_500, &culture).await;
|
||
|
||
let (st, b) = app.get("/stats/periods", &s).await;
|
||
assert_eq!(st, StatusCode::OK, "{b}");
|
||
assert_eq!(b["total_saved"], 500_000);
|
||
let list = b["periods"].as_array().unwrap();
|
||
assert_eq!(list.len(), 2);
|
||
// Eskiden yeniye
|
||
assert_eq!(list[0]["id"], nov.as_str());
|
||
assert_eq!(list[0]["status"], "closed");
|
||
assert_eq!(list[0]["spendable"], 1_000_000);
|
||
assert_eq!(list[0]["spent"], 300_000);
|
||
assert_eq!(
|
||
list[0]["spent_by_group"],
|
||
json!({ "needs": 300_000, "wants": 0, "culture": 0, "unexpected": 0 })
|
||
);
|
||
assert_eq!(list[0]["to_savings"], 500_000);
|
||
assert_eq!(list[0]["last_date"], "2026-11-30");
|
||
assert_eq!(list[1]["id"], dec.as_str());
|
||
assert_eq!(
|
||
list[1]["spendable"], 2_100_000,
|
||
"20.000 − 1.000 hedef + 2.000 artı"
|
||
);
|
||
assert_eq!(list[1]["spent"], 42_500);
|
||
assert_eq!(list[1]["spent_by_group"]["needs"], 40_000);
|
||
assert_eq!(list[1]["spent_by_group"]["culture"], 2_500);
|
||
assert_eq!(list[1]["income_total"], 2_000_000);
|
||
assert_eq!(list[1]["savings_goal"], 100_000);
|
||
assert_eq!(list[1]["carryover_surplus"], 200_000);
|
||
assert_eq!(
|
||
list[0]["realized_savings"], 700_000,
|
||
"Kasım: 10.000 − 3.000"
|
||
);
|
||
assert_eq!(
|
||
list[1]["realized_savings"], 2_157_500,
|
||
"20.000 + 2.000 artı − 425 harcanan (hedef düşülmez)"
|
||
);
|
||
assert_eq!(list[1]["to_savings"], Value::Null);
|
||
|
||
// limit: en yeni N dönem
|
||
let (_, b) = app.get("/stats/periods?limit=1", &s).await;
|
||
let list = b["periods"].as_array().unwrap();
|
||
assert_eq!(list.len(), 1);
|
||
assert_eq!(list[0]["id"], dec.as_str());
|
||
assert_eq!(
|
||
b["total_saved"], 500_000,
|
||
"toplam birikim limitten bağımsız"
|
||
);
|
||
|
||
// Başka hane: boş
|
||
let other = app.register("Diger", None).await;
|
||
let (_, b) = app.get("/stats/periods", &other).await;
|
||
assert_eq!(b["total_saved"], 0);
|
||
assert_eq!(b["periods"], json!([]));
|
||
app.cleanup().await;
|
||
}
|