Display balance evolution chart

This commit is contained in:
2025-05-02 11:17:51 +02:00
parent 272c8ab312
commit 56370ec936
9 changed files with 561 additions and 43 deletions

View File

@ -1,7 +1,10 @@
use crate::controllers::HttpResult;
use crate::extractors::auth_extractor::AuthExtractor;
use crate::services::{files_service, movements_service};
use actix_web::HttpResponse;
use crate::models::accounts::Account;
use crate::services::{accounts_service, files_service, movements_service};
use crate::utils::time_utils::time;
use actix_web::{HttpResponse, web};
use std::collections::HashMap;
#[derive(serde::Serialize)]
struct GlobalStats {
@ -23,3 +26,75 @@ pub async fn global(auth: AuthExtractor) -> HttpResult {
total_files_size: files.iter().fold(0, |sum, m| sum + m.file_size as u64),
}))
}
#[derive(serde::Deserialize)]
pub struct BalanceVariationQuery {
#[serde(skip_serializing_if = "Option::is_none")]
interval: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
start: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
end: Option<i64>,
}
/// Statistic dataset entry
#[derive(serde::Serialize, Debug, Clone)]
struct StatEntry {
time: i64,
/// Account balances. Note: due to JSON limitation, we had to turn account id into strings
#[serde(flatten)]
balances: HashMap<String, f32>,
}
impl StatEntry {
fn init_first(time: i64, accounts: &[Account]) -> Self {
Self {
time,
balances: accounts
.iter()
.map(|a| (a.id().0.to_string(), 0.0))
.collect(),
}
}
}
/// Accounts balance variation
pub async fn balance_variation(
auth: AuthExtractor,
query: web::Query<BalanceVariationQuery>,
) -> HttpResult {
let start = query.start.unwrap_or((time() - 3600 * 24 * 30) as i64);
let end = query.end.unwrap_or(time() as i64);
let interval = query.interval.unwrap_or(3600 * 24);
let accounts = accounts_service::get_list_user(auth.user.id()).await?;
let mut movements = movements_service::get_all_movements_user(auth.user.id()).await?;
movements.sort_by(|a, b| a.time.cmp(&b.time));
let mut dataset = vec![];
let mut stat_entry = StatEntry::init_first(start, &accounts);
// Iter movements
for m in movements {
if m.time > end {
break;
}
// Check if it is time to go the the next entry
while m.time > stat_entry.time {
dataset.push(stat_entry.clone());
stat_entry.time += interval as i64;
}
let target_account_id = m.account_id().0.to_string();
let old_amount = *stat_entry.balances.get(&target_account_id).unwrap_or(&0.0);
stat_entry
.balances
.insert(target_account_id, old_amount + m.amount);
}
// Final push
dataset.push(stat_entry);
Ok(HttpResponse::Ok().json(dataset))
}