use std::cmp; use chrono::{Datelike, Local, NaiveDate}; use serde::Serialize; use sqlx::SqlitePool; use crate::model::{ logbook::{Filter, Logbook, LogbookWithBoatAndRowers}, stat::Stat, user::User, }; enum AgeBracket { Till14, From14Till18, From19Till30, From31Till60, From61Till75, From76, } impl AgeBracket { fn cat(&self) -> &str { match self { AgeBracket::Till14 => "Schülerinnen und Schüler bis 14 Jahre", AgeBracket::From14Till18 => "Juniorinnen und Junioren, Para-Ruderer bis 18 Jahre", AgeBracket::From19Till30 => "Frauen und Männer, Para-Ruderer bis 30 Jahre", AgeBracket::From31Till60 => "Frauen und Männer, Para-Ruderer von 31 bis 60 Jahre", AgeBracket::From61Till75 => "Frauen und Männer, Para-Ruderer von 61 bis 75 Jahre", AgeBracket::From76 => "Frauen und Männer, Para-Ruderer ab 76 Jahre", } } fn dist_in_km(&self) -> i32 { match self { AgeBracket::Till14 => 500, AgeBracket::From14Till18 => 1000, AgeBracket::From19Till30 => 1200, AgeBracket::From31Till60 => 1000, AgeBracket::From61Till75 => 800, AgeBracket::From76 => 600, } } fn required_dist_multi_day_in_km(&self) -> i32 { match self { AgeBracket::Till14 => 60, AgeBracket::From14Till18 => 60, AgeBracket::From19Till30 => 80, AgeBracket::From31Till60 => 80, AgeBracket::From61Till75 => 80, AgeBracket::From76 => 80, } } fn required_dist_single_day_in_km(&self) -> i32 { match self { AgeBracket::Till14 => 30, AgeBracket::From14Till18 => 30, AgeBracket::From19Till30 => 40, AgeBracket::From31Till60 => 40, AgeBracket::From61Till75 => 40, AgeBracket::From76 => 40, } } } impl TryFrom<&User> for AgeBracket { type Error = String; fn try_from(value: &User) -> Result { let Some(birthdate) = value.birthdate.clone() else { return Err("User has no birthdate".to_string()); }; let Ok(birthdate) = NaiveDate::parse_from_str(&birthdate, "%Y-%m-%d") else { return Err("Birthdate in wrong format...".to_string()); }; let today = Local::now().date_naive(); let age = today.year() - birthdate.year(); if age <= 14 { Ok(AgeBracket::Till14) } else if age <= 18 { Ok(AgeBracket::From14Till18) } else if age <= 30 { Ok(AgeBracket::From19Till30) } else if age <= 60 { Ok(AgeBracket::From31Till60) } else if age <= 75 { Ok(AgeBracket::From61Till75) } else { Ok(AgeBracket::From76) } } } #[derive(Serialize)] pub(crate) struct Status { pub(crate) year: i32, rowed_km: i32, category: String, required_km: i32, missing_km: i32, multi_day_trips_over_required_distance: Vec, multi_day_trips_required_distance: i32, single_day_trips_over_required_distance: Vec, single_day_trips_required_distance: i32, achieved: bool, } impl Status { pub(crate) async fn for_user(db: &SqlitePool, user: &User) -> Option { let Ok(agebracket) = AgeBracket::try_from(user) else { return None; }; let category = agebracket.cat().to_string(); let year = if Local::now().month() == 1 { Local::now().year() - 1 } else { Local::now().year() }; let rowed_km = Stat::person(db, Some(year), user).await.rowed_km; let required_km = agebracket.dist_in_km(); let missing_km = cmp::max(required_km - rowed_km, 0); let single_day_trips_over_required_distance = Logbook::completed_wanderfahrten_with_user_over_km_in_year( db, user, agebracket.required_dist_single_day_in_km(), year, Filter::SingleDayOnly, ) .await; let multi_day_trips_over_required_distance = Logbook::completed_wanderfahrten_with_user_over_km_in_year( db, user, agebracket.required_dist_multi_day_in_km(), year, Filter::MultiDazOnly, ) .await; let achieved = missing_km == 0 && (multi_day_trips_over_required_distance.len() >= 1 || single_day_trips_over_required_distance.len() >= 2); Some(Self { year, rowed_km, category, required_km, missing_km, multi_day_trips_over_required_distance, single_day_trips_over_required_distance, multi_day_trips_required_distance: agebracket.required_dist_multi_day_in_km(), single_day_trips_required_distance: agebracket.required_dist_single_day_in_km(), achieved, }) } }