222 lines
6.7 KiB
Rust
222 lines
6.7 KiB
Rust
use std::cmp;
|
|
|
|
use chrono::{Datelike, Local, NaiveDate};
|
|
use serde::Serialize;
|
|
use sqlx::{Sqlite, SqlitePool, Transaction};
|
|
|
|
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<Self, Self::Error> {
|
|
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<LogbookWithBoatAndRowers>,
|
|
multi_day_trips_required_distance: i32,
|
|
single_day_trips_over_required_distance: Vec<LogbookWithBoatAndRowers>,
|
|
single_day_trips_required_distance: i32,
|
|
achieved: bool,
|
|
}
|
|
|
|
impl Status {
|
|
fn calc(
|
|
agebracket: &AgeBracket,
|
|
rowed_km: i32,
|
|
single_day_trips_over_required_distance: usize,
|
|
multi_day_trips_over_required_distance: usize,
|
|
year: i32,
|
|
) -> Self {
|
|
let category = agebracket.cat().to_string();
|
|
|
|
let required_km = agebracket.dist_in_km();
|
|
let missing_km = cmp::max(required_km - rowed_km, 0);
|
|
|
|
let achieved = missing_km == 0
|
|
&& (multi_day_trips_over_required_distance >= 1
|
|
|| single_day_trips_over_required_distance >= 2);
|
|
|
|
Self {
|
|
year,
|
|
rowed_km,
|
|
category,
|
|
required_km,
|
|
missing_km,
|
|
multi_day_trips_over_required_distance: vec![],
|
|
single_day_trips_over_required_distance: vec![],
|
|
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,
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn for_user_tx(
|
|
db: &mut Transaction<'_, Sqlite>,
|
|
user: &User,
|
|
exclude_last_log: bool,
|
|
) -> Option<Self> {
|
|
let Ok(agebracket) = AgeBracket::try_from(user) else {
|
|
return None;
|
|
};
|
|
|
|
let year = if Local::now().month() == 1 {
|
|
Local::now().year() - 1
|
|
} else {
|
|
Local::now().year()
|
|
};
|
|
|
|
let rowed_km = Stat::person_tx(db, Some(year), user).await.rowed_km;
|
|
let single_day_trips_over_required_distance =
|
|
Logbook::completed_wanderfahrten_with_user_over_km_in_year_tx(
|
|
db,
|
|
user,
|
|
agebracket.required_dist_single_day_in_km(),
|
|
year,
|
|
Filter::SingleDayOnly,
|
|
exclude_last_log,
|
|
)
|
|
.await;
|
|
let multi_day_trips_over_required_distance =
|
|
Logbook::completed_wanderfahrten_with_user_over_km_in_year_tx(
|
|
db,
|
|
user,
|
|
agebracket.required_dist_multi_day_in_km(),
|
|
year,
|
|
Filter::MultiDayOnly,
|
|
exclude_last_log,
|
|
)
|
|
.await;
|
|
|
|
let ret = Self::calc(
|
|
&agebracket,
|
|
rowed_km,
|
|
single_day_trips_over_required_distance.len(),
|
|
multi_day_trips_over_required_distance.len(),
|
|
year,
|
|
);
|
|
|
|
Some(Self {
|
|
multi_day_trips_over_required_distance,
|
|
single_day_trips_over_required_distance,
|
|
..ret
|
|
})
|
|
}
|
|
|
|
pub(crate) async fn for_user(db: &SqlitePool, user: &User) -> Option<Self> {
|
|
let mut tx = db.begin().await.unwrap();
|
|
let ret = Self::for_user_tx(&mut tx, user, false).await;
|
|
tx.commit().await.unwrap();
|
|
ret
|
|
}
|
|
|
|
pub(crate) async fn completed_with_last_log(
|
|
db: &mut Transaction<'_, Sqlite>,
|
|
user: &User,
|
|
) -> bool {
|
|
if let Some(status) = Self::for_user_tx(db, user, false).await {
|
|
// if user has agebracket...
|
|
if status.achieved {
|
|
// ... and has achieved the 'Fahrtenabzeichen'
|
|
let without_last_entry = Self::for_user_tx(db, user, true).await.unwrap();
|
|
if !without_last_entry.achieved {
|
|
// ... and this wasn't the case before the last logentry
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
}
|