forked from Ruderverein-Donau-Linz/rowt
		
	Merge pull request 'notfiication-on-new-personal-stat' (#843) from notfiication-on-new-personal-stat into main
Reviewed-on: Ruderverein-Donau-Linz/rowt#843
This commit is contained in:
		@@ -1,5 +1,3 @@
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use rocket::serde::{Deserialize, Serialize};
 | 
			
		||||
use sqlx::{FromRow, SqlitePool};
 | 
			
		||||
 | 
			
		||||
@@ -7,6 +5,93 @@ use crate::tera::board::boathouse::FormBoathouseToAdd;
 | 
			
		||||
 | 
			
		||||
use super::boat::Boat;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct BoathousePlace {
 | 
			
		||||
    boat: Boat,
 | 
			
		||||
    boathouse_id: i64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct BoathouseRack {
 | 
			
		||||
    boats: [Option<BoathousePlace>; 12],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BoathouseRack {
 | 
			
		||||
    fn new() -> Self {
 | 
			
		||||
        let boats = [
 | 
			
		||||
            None, None, None, None, None, None, None, None, None, None, None, None,
 | 
			
		||||
        ];
 | 
			
		||||
        Self { boats }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn add(&mut self, db: &SqlitePool, boathouse: Boathouse) {
 | 
			
		||||
        self.boats[boathouse.level as usize] = Some(BoathousePlace {
 | 
			
		||||
            boat: Boat::find_by_id(db, boathouse.boat_id as i32)
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap(),
 | 
			
		||||
            boathouse_id: boathouse.id,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct BoathouseSide {
 | 
			
		||||
    mountain: BoathouseRack,
 | 
			
		||||
    water: BoathouseRack,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BoathouseSide {
 | 
			
		||||
    fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            mountain: BoathouseRack::new(),
 | 
			
		||||
            water: BoathouseRack::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn add(&mut self, db: &SqlitePool, boathouse: Boathouse) {
 | 
			
		||||
        match boathouse.side.as_str() {
 | 
			
		||||
            "mountain" => self.mountain.add(db, boathouse).await,
 | 
			
		||||
            "water" => self.water.add(db, boathouse).await,
 | 
			
		||||
            _ => panic!("db constraint failed"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct BoathouseAisles {
 | 
			
		||||
    mountain: BoathouseSide,
 | 
			
		||||
    middle: BoathouseSide,
 | 
			
		||||
    water: BoathouseSide,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BoathouseAisles {
 | 
			
		||||
    fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            mountain: BoathouseSide::new(),
 | 
			
		||||
            middle: BoathouseSide::new(),
 | 
			
		||||
            water: BoathouseSide::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn add(&mut self, db: &SqlitePool, boathouse: Boathouse) {
 | 
			
		||||
        match boathouse.aisle.as_str() {
 | 
			
		||||
            "water" => self.water.add(db, boathouse).await,
 | 
			
		||||
            "middle" => self.middle.add(db, boathouse).await,
 | 
			
		||||
            "mountain" => self.mountain.add(db, boathouse).await,
 | 
			
		||||
            _ => panic!("db constraint failed"),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn from(db: &SqlitePool, boathouses: Vec<Boathouse>) -> Self {
 | 
			
		||||
        let mut ret = BoathouseAisles::new();
 | 
			
		||||
 | 
			
		||||
        for boathouse in boathouses {
 | 
			
		||||
            ret.add(db, boathouse).await;
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(FromRow, Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct Boathouse {
 | 
			
		||||
    pub id: i64,
 | 
			
		||||
@@ -17,54 +102,7 @@ pub struct Boathouse {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Boathouse {
 | 
			
		||||
    pub async fn get(db: &SqlitePool) -> HashMap<&str, HashMap<&str, [Option<(i64, Boat)>; 12]>> {
 | 
			
		||||
        let mut ret: HashMap<&str, HashMap<&str, [Option<(i64, Boat)>; 12]>> = HashMap::new();
 | 
			
		||||
 | 
			
		||||
        let mut mountain = HashMap::new();
 | 
			
		||||
        mountain.insert(
 | 
			
		||||
            "mountain",
 | 
			
		||||
            [
 | 
			
		||||
                None, None, None, None, None, None, None, None, None, None, None, None,
 | 
			
		||||
            ],
 | 
			
		||||
        );
 | 
			
		||||
        mountain.insert(
 | 
			
		||||
            "water",
 | 
			
		||||
            [
 | 
			
		||||
                None, None, None, None, None, None, None, None, None, None, None, None,
 | 
			
		||||
            ],
 | 
			
		||||
        );
 | 
			
		||||
        ret.insert("mountain-aisle", mountain);
 | 
			
		||||
 | 
			
		||||
        let mut middle = HashMap::new();
 | 
			
		||||
        middle.insert(
 | 
			
		||||
            "mountain",
 | 
			
		||||
            [
 | 
			
		||||
                None, None, None, None, None, None, None, None, None, None, None, None,
 | 
			
		||||
            ],
 | 
			
		||||
        );
 | 
			
		||||
        middle.insert(
 | 
			
		||||
            "water",
 | 
			
		||||
            [
 | 
			
		||||
                None, None, None, None, None, None, None, None, None, None, None, None,
 | 
			
		||||
            ],
 | 
			
		||||
        );
 | 
			
		||||
        ret.insert("middle-aisle", middle);
 | 
			
		||||
 | 
			
		||||
        let mut water = HashMap::new();
 | 
			
		||||
        water.insert(
 | 
			
		||||
            "mountain",
 | 
			
		||||
            [
 | 
			
		||||
                None, None, None, None, None, None, None, None, None, None, None, None,
 | 
			
		||||
            ],
 | 
			
		||||
        );
 | 
			
		||||
        water.insert(
 | 
			
		||||
            "water",
 | 
			
		||||
            [
 | 
			
		||||
                None, None, None, None, None, None, None, None, None, None, None, None,
 | 
			
		||||
            ],
 | 
			
		||||
        );
 | 
			
		||||
        ret.insert("water-aisle", water);
 | 
			
		||||
 | 
			
		||||
    pub async fn get(db: &SqlitePool) -> BoathouseAisles {
 | 
			
		||||
        let boathouses = sqlx::query_as!(
 | 
			
		||||
            Boathouse,
 | 
			
		||||
            "SELECT id, boat_id, aisle, side, level FROM boathouse"
 | 
			
		||||
@@ -73,21 +111,7 @@ impl Boathouse {
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap(); //TODO: fixme
 | 
			
		||||
 | 
			
		||||
        for boathouse in boathouses {
 | 
			
		||||
            let aisle = ret
 | 
			
		||||
                .get_mut(format!("{}-aisle", boathouse.aisle).as_str())
 | 
			
		||||
                .unwrap();
 | 
			
		||||
            let side = aisle.get_mut(boathouse.side.as_str()).unwrap();
 | 
			
		||||
 | 
			
		||||
            side[boathouse.level as usize] = Some((
 | 
			
		||||
                boathouse.id,
 | 
			
		||||
                Boat::find_by_id(db, boathouse.boat_id as i32)
 | 
			
		||||
                    .await
 | 
			
		||||
                    .unwrap(),
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ret
 | 
			
		||||
        BoathouseAisles::from(db, boathouses).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn create(db: &SqlitePool, data: FormBoathouseToAdd) -> Result<(), String> {
 | 
			
		||||
 
 | 
			
		||||
@@ -33,8 +33,7 @@ impl PartialEq for Logbook {
 | 
			
		||||
 | 
			
		||||
pub(crate) enum Filter {
 | 
			
		||||
    SingleDayOnly,
 | 
			
		||||
    MultiDazOnly,
 | 
			
		||||
    None,
 | 
			
		||||
    MultiDayOnly,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(FromForm, Debug, Clone)]
 | 
			
		||||
@@ -362,12 +361,13 @@ ORDER BY departure DESC
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) async fn completed_wanderfahrten_with_user_over_km_in_year(
 | 
			
		||||
        db: &SqlitePool,
 | 
			
		||||
    pub(crate) async fn completed_wanderfahrten_with_user_over_km_in_year_tx(
 | 
			
		||||
        db: &mut Transaction<'_, Sqlite>,
 | 
			
		||||
        user: &User,
 | 
			
		||||
        min_distance: i32,
 | 
			
		||||
        year: i32,
 | 
			
		||||
        filter: Filter,
 | 
			
		||||
        exclude_last_log: bool,
 | 
			
		||||
    ) -> Vec<LogbookWithBoatAndRowers> {
 | 
			
		||||
        let logs: Vec<Logbook> = sqlx::query_as(
 | 
			
		||||
               &format!("
 | 
			
		||||
@@ -378,7 +378,7 @@ ORDER BY departure DESC
 | 
			
		||||
    ORDER BY arrival DESC
 | 
			
		||||
            ",  user.id, min_distance, year)
 | 
			
		||||
            )
 | 
			
		||||
            .fetch_all(db)
 | 
			
		||||
            .fetch_all(db.deref_mut())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap(); //TODO: fixme
 | 
			
		||||
 | 
			
		||||
@@ -389,19 +389,20 @@ ORDER BY departure DESC
 | 
			
		||||
            match filter {
 | 
			
		||||
                Filter::SingleDayOnly => {
 | 
			
		||||
                    if trip_days == 0 {
 | 
			
		||||
                        ret.push(LogbookWithBoatAndRowers::from(db, log).await);
 | 
			
		||||
                        ret.push(LogbookWithBoatAndRowers::from_tx(db, log).await);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Filter::MultiDazOnly => {
 | 
			
		||||
                Filter::MultiDayOnly => {
 | 
			
		||||
                    if trip_days > 0 {
 | 
			
		||||
                        ret.push(LogbookWithBoatAndRowers::from(db, log).await);
 | 
			
		||||
                        ret.push(LogbookWithBoatAndRowers::from_tx(db, log).await);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Filter::None => {
 | 
			
		||||
                    ret.push(LogbookWithBoatAndRowers::from(db, log).await);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if exclude_last_log {
 | 
			
		||||
            ret.pop();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,63 +1,64 @@
 | 
			
		||||
use crate::model::{logbook::Logbook, stat::Stat, user::User};
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, PartialEq, Debug)]
 | 
			
		||||
pub(crate) enum Level {
 | 
			
		||||
    NONE,
 | 
			
		||||
    BRONZE,
 | 
			
		||||
    SILVER,
 | 
			
		||||
    GOLD,
 | 
			
		||||
    DIAMOND,
 | 
			
		||||
    DONE,
 | 
			
		||||
    None,
 | 
			
		||||
    Bronze,
 | 
			
		||||
    Silver,
 | 
			
		||||
    Gold,
 | 
			
		||||
    Diamond,
 | 
			
		||||
    Done,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Level {
 | 
			
		||||
    fn required_km(&self) -> i32 {
 | 
			
		||||
        match self {
 | 
			
		||||
            Level::BRONZE => 40000,
 | 
			
		||||
            Level::SILVER => 80000,
 | 
			
		||||
            Level::GOLD => 100000,
 | 
			
		||||
            Level::DIAMOND => 200000,
 | 
			
		||||
            Level::DONE => 0,
 | 
			
		||||
            Level::NONE => 0,
 | 
			
		||||
            Level::Bronze => 40_000,
 | 
			
		||||
            Level::Silver => 80_000,
 | 
			
		||||
            Level::Gold => 100_000,
 | 
			
		||||
            Level::Diamond => 200_000,
 | 
			
		||||
            Level::Done => 0,
 | 
			
		||||
            Level::None => 0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn next_level(km: i32) -> Self {
 | 
			
		||||
        if km < Level::BRONZE.required_km() {
 | 
			
		||||
            Level::BRONZE
 | 
			
		||||
        } else if km < Level::SILVER.required_km() {
 | 
			
		||||
            Level::SILVER
 | 
			
		||||
        } else if km < Level::GOLD.required_km() {
 | 
			
		||||
            Level::GOLD
 | 
			
		||||
        } else if km < Level::DIAMOND.required_km() {
 | 
			
		||||
            Level::DIAMOND
 | 
			
		||||
        if km < Level::Bronze.required_km() {
 | 
			
		||||
            Level::Bronze
 | 
			
		||||
        } else if km < Level::Silver.required_km() {
 | 
			
		||||
            Level::Silver
 | 
			
		||||
        } else if km < Level::Gold.required_km() {
 | 
			
		||||
            Level::Gold
 | 
			
		||||
        } else if km < Level::Diamond.required_km() {
 | 
			
		||||
            Level::Diamond
 | 
			
		||||
        } else {
 | 
			
		||||
            Level::DONE
 | 
			
		||||
            Level::Done
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn curr_level(km: i32) -> Self {
 | 
			
		||||
        if km < Level::BRONZE.required_km() {
 | 
			
		||||
            Level::NONE
 | 
			
		||||
        } else if km < Level::SILVER.required_km() {
 | 
			
		||||
            Level::BRONZE
 | 
			
		||||
        } else if km < Level::GOLD.required_km() {
 | 
			
		||||
            Level::SILVER
 | 
			
		||||
        } else if km < Level::DIAMOND.required_km() {
 | 
			
		||||
            Level::GOLD
 | 
			
		||||
        if km < Level::Bronze.required_km() {
 | 
			
		||||
            Level::None
 | 
			
		||||
        } else if km < Level::Silver.required_km() {
 | 
			
		||||
            Level::Bronze
 | 
			
		||||
        } else if km < Level::Gold.required_km() {
 | 
			
		||||
            Level::Silver
 | 
			
		||||
        } else if km < Level::Diamond.required_km() {
 | 
			
		||||
            Level::Gold
 | 
			
		||||
        } else {
 | 
			
		||||
            Level::DIAMOND
 | 
			
		||||
            Level::Diamond
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn desc(&self) -> &str {
 | 
			
		||||
        match self {
 | 
			
		||||
            Level::BRONZE => "Bronze",
 | 
			
		||||
            Level::SILVER => "Silber",
 | 
			
		||||
            Level::GOLD => "Gold",
 | 
			
		||||
            Level::DIAMOND => "Diamant",
 | 
			
		||||
            Level::DONE => "",
 | 
			
		||||
            Level::NONE => "-",
 | 
			
		||||
            Level::Bronze => "Bronze",
 | 
			
		||||
            Level::Silver => "Silber",
 | 
			
		||||
            Level::Gold => "Gold",
 | 
			
		||||
            Level::Diamond => "Diamant",
 | 
			
		||||
            Level::Done => "",
 | 
			
		||||
            Level::None => "-",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -85,3 +86,19 @@ impl Next {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) async fn new_level_with_last_log(
 | 
			
		||||
    db: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
 | 
			
		||||
    user: &User,
 | 
			
		||||
) -> Option<String> {
 | 
			
		||||
    let rowed_km = Stat::total_km_tx(db, user).await.rowed_km;
 | 
			
		||||
 | 
			
		||||
    if let Some(last_logbookentry) = Logbook::completed_with_user_tx(db, user).await.last() {
 | 
			
		||||
        let last_trip_km = last_logbookentry.logbook.distance_in_km.unwrap();
 | 
			
		||||
        if Level::curr_level(rowed_km) != Level::curr_level(rowed_km - last_trip_km as i32) {
 | 
			
		||||
            return Some(Level::curr_level(rowed_km).desc().to_string());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    None
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ use std::cmp;
 | 
			
		||||
 | 
			
		||||
use chrono::{Datelike, Local, NaiveDate};
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use sqlx::SqlitePool;
 | 
			
		||||
use sqlx::{Sqlite, SqlitePool, Transaction};
 | 
			
		||||
 | 
			
		||||
use crate::model::{
 | 
			
		||||
    logbook::{Filter, Logbook, LogbookWithBoatAndRowers},
 | 
			
		||||
@@ -111,11 +111,44 @@ pub(crate) struct Status {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Status {
 | 
			
		||||
    pub(crate) async fn for_user(db: &SqlitePool, user: &User) -> Option<Self> {
 | 
			
		||||
    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 category = agebracket.cat().to_string();
 | 
			
		||||
 | 
			
		||||
        let year = if Local::now().month() == 1 {
 | 
			
		||||
            Local::now().year() - 1
 | 
			
		||||
@@ -123,44 +156,66 @@ impl Status {
 | 
			
		||||
            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 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(
 | 
			
		||||
            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(
 | 
			
		||||
            Logbook::completed_wanderfahrten_with_user_over_km_in_year_tx(
 | 
			
		||||
                db,
 | 
			
		||||
                user,
 | 
			
		||||
                agebracket.required_dist_multi_day_in_km(),
 | 
			
		||||
                year,
 | 
			
		||||
                Filter::MultiDazOnly,
 | 
			
		||||
                Filter::MultiDayOnly,
 | 
			
		||||
                exclude_last_log,
 | 
			
		||||
            )
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
        let achieved = missing_km == 0
 | 
			
		||||
            && (multi_day_trips_over_required_distance.len() >= 1
 | 
			
		||||
                || single_day_trips_over_required_distance.len() >= 2);
 | 
			
		||||
        let ret = Self::calc(
 | 
			
		||||
            &agebracket,
 | 
			
		||||
            rowed_km,
 | 
			
		||||
            single_day_trips_over_required_distance.len(),
 | 
			
		||||
            multi_day_trips_over_required_distance.len(),
 | 
			
		||||
            year,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        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,
 | 
			
		||||
            ..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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::{collections::HashMap, ops::DerefMut};
 | 
			
		||||
 | 
			
		||||
use crate::model::user::User;
 | 
			
		||||
use chrono::Datelike;
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use sqlx::{FromRow, Row, SqlitePool};
 | 
			
		||||
use sqlx::{FromRow, Row, Sqlite, SqlitePool, Transaction};
 | 
			
		||||
 | 
			
		||||
use super::boat::Boat;
 | 
			
		||||
 | 
			
		||||
@@ -218,7 +218,7 @@ ORDER BY rowed_km DESC, u.name;
 | 
			
		||||
        .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn total_km(db: &SqlitePool, user: &User) -> Stat {
 | 
			
		||||
    pub async fn total_km_tx(db: &mut Transaction<'_, Sqlite>, user: &User) -> Stat {
 | 
			
		||||
        //TODO: switch to query! macro again (once upgraded to sqlite 3.42 on server)
 | 
			
		||||
        let row = sqlx::query(&format!(
 | 
			
		||||
            "
 | 
			
		||||
@@ -233,7 +233,7 @@ WHERE l.distance_in_km IS NOT NULL;
 | 
			
		||||
",
 | 
			
		||||
            user.id
 | 
			
		||||
        ))
 | 
			
		||||
        .fetch_one(db)
 | 
			
		||||
        .fetch_one(db.deref_mut())
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
@@ -244,7 +244,18 @@ WHERE l.distance_in_km IS NOT NULL;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn person(db: &SqlitePool, year: Option<i32>, user: &User) -> Stat {
 | 
			
		||||
    pub async fn total_km(db: &SqlitePool, user: &User) -> Stat {
 | 
			
		||||
        let mut tx = db.begin().await.unwrap();
 | 
			
		||||
        let ret = Self::total_km_tx(&mut tx, user).await;
 | 
			
		||||
        tx.commit().await.unwrap();
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn person_tx(
 | 
			
		||||
        db: &mut Transaction<'_, Sqlite>,
 | 
			
		||||
        year: Option<i32>,
 | 
			
		||||
        user: &User,
 | 
			
		||||
    ) -> Stat {
 | 
			
		||||
        let year = match year {
 | 
			
		||||
            Some(year) => year,
 | 
			
		||||
            None => chrono::Local::now().year(),
 | 
			
		||||
@@ -263,7 +274,7 @@ WHERE l.distance_in_km IS NOT NULL AND l.arrival LIKE '{year}-%';
 | 
			
		||||
",
 | 
			
		||||
            user.id
 | 
			
		||||
        ))
 | 
			
		||||
        .fetch_one(db)
 | 
			
		||||
        .fetch_one(db.deref_mut())
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
@@ -273,6 +284,13 @@ WHERE l.distance_in_km IS NOT NULL AND l.arrival LIKE '{year}-%';
 | 
			
		||||
            rowed_km: row.get("rowed_km"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn person(db: &SqlitePool, year: Option<i32>, user: &User) -> Stat {
 | 
			
		||||
        let mut tx = db.begin().await.unwrap();
 | 
			
		||||
        let ret = Self::person_tx(&mut tx, year, user).await;
 | 
			
		||||
        tx.commit().await.unwrap();
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
 
 | 
			
		||||
@@ -287,10 +287,8 @@ WHERE day=?
 | 
			
		||||
            return Err(TripUpdateError::NotYourTrip);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if update.trip_type != Some(4) {
 | 
			
		||||
            if !update.cox.allowed_to_steer(db).await {
 | 
			
		||||
                return Err(TripUpdateError::TripTypeNotAllowed);
 | 
			
		||||
            }
 | 
			
		||||
        if update.trip_type != Some(4) && !update.cox.allowed_to_steer(db).await {
 | 
			
		||||
            return Err(TripUpdateError::TripTypeNotAllowed);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let Some(trip_details_id) = update.trip.trip_details_id else {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								src/model/user/fee.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/model/user/fee.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
use super::User;
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
pub struct Fee {
 | 
			
		||||
    pub sum_in_cents: i32,
 | 
			
		||||
    pub parts: Vec<(String, i32)>,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub user_ids: String,
 | 
			
		||||
    pub paid: bool,
 | 
			
		||||
    pub users: Vec<User>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Fee {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::new()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Fee {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            sum_in_cents: 0,
 | 
			
		||||
            name: "".into(),
 | 
			
		||||
            parts: Vec::new(),
 | 
			
		||||
            user_ids: "".into(),
 | 
			
		||||
            users: Vec::new(),
 | 
			
		||||
            paid: false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add(&mut self, desc: String, price_in_cents: i32) {
 | 
			
		||||
        self.sum_in_cents += price_in_cents;
 | 
			
		||||
 | 
			
		||||
        self.parts.push((desc, price_in_cents));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_person(&mut self, user: &User) {
 | 
			
		||||
        if !self.name.is_empty() {
 | 
			
		||||
            self.name.push_str(" + ");
 | 
			
		||||
            self.user_ids.push('&');
 | 
			
		||||
        }
 | 
			
		||||
        self.name.push_str(&user.name);
 | 
			
		||||
 | 
			
		||||
        self.user_ids.push_str(&format!("user_ids[]={}", user.id));
 | 
			
		||||
        self.users.push(user.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn paid(&mut self) {
 | 
			
		||||
        self.paid = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn merge(&mut self, fee: Fee) {
 | 
			
		||||
        for (desc, price_in_cents) in fee.parts {
 | 
			
		||||
            self.add(desc, price_in_cents);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,14 +15,25 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
    family::Family, log::Log, logbook::Logbook, mail::Mail, notification::Notification, role::Role,
 | 
			
		||||
    stat::Stat, tripdetails::TripDetails, Day,
 | 
			
		||||
    family::Family,
 | 
			
		||||
    log::Log,
 | 
			
		||||
    logbook::Logbook,
 | 
			
		||||
    mail::Mail,
 | 
			
		||||
    notification::Notification,
 | 
			
		||||
    personal::{equatorprice, rowingbadge},
 | 
			
		||||
    role::Role,
 | 
			
		||||
    stat::Stat,
 | 
			
		||||
    tripdetails::TripDetails,
 | 
			
		||||
    Day,
 | 
			
		||||
};
 | 
			
		||||
use crate::{
 | 
			
		||||
    tera::admin::user::UserEditForm, AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD, BOAT_STORAGE,
 | 
			
		||||
    EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND, REGULAR, RENNRUDERBEITRAG,
 | 
			
		||||
    SCHECKBUCH, STUDENT_OR_PUPIL, UNTERSTUETZEND,
 | 
			
		||||
};
 | 
			
		||||
use fee::Fee;
 | 
			
		||||
 | 
			
		||||
mod fee;
 | 
			
		||||
 | 
			
		||||
#[derive(FromRow, Serialize, Deserialize, Clone, Debug, Eq, Hash, PartialEq)]
 | 
			
		||||
pub struct User {
 | 
			
		||||
@@ -83,62 +94,6 @@ pub enum LoginError {
 | 
			
		||||
    DeserializationError,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
pub struct Fee {
 | 
			
		||||
    pub sum_in_cents: i32,
 | 
			
		||||
    pub parts: Vec<(String, i32)>,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub user_ids: String,
 | 
			
		||||
    pub paid: bool,
 | 
			
		||||
    pub users: Vec<User>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Fee {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::new()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Fee {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            sum_in_cents: 0,
 | 
			
		||||
            name: "".into(),
 | 
			
		||||
            parts: Vec::new(),
 | 
			
		||||
            user_ids: "".into(),
 | 
			
		||||
            users: Vec::new(),
 | 
			
		||||
            paid: false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add(&mut self, desc: String, price_in_cents: i32) {
 | 
			
		||||
        self.sum_in_cents += price_in_cents;
 | 
			
		||||
 | 
			
		||||
        self.parts.push((desc, price_in_cents));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_person(&mut self, user: &User) {
 | 
			
		||||
        if !self.name.is_empty() {
 | 
			
		||||
            self.name.push_str(" + ");
 | 
			
		||||
            self.user_ids.push('&');
 | 
			
		||||
        }
 | 
			
		||||
        self.name.push_str(&user.name);
 | 
			
		||||
 | 
			
		||||
        self.user_ids.push_str(&format!("user_ids[]={}", user.id));
 | 
			
		||||
        self.users.push(user.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn paid(&mut self) {
 | 
			
		||||
        self.paid = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn merge(&mut self, fee: Fee) {
 | 
			
		||||
        for (desc, price_in_cents) in fee.parts {
 | 
			
		||||
            self.add(desc, price_in_cents);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl User {
 | 
			
		||||
    pub async fn allowed_to_steer(&self, db: &SqlitePool) -> bool {
 | 
			
		||||
        self.has_role(db, "cox").await || self.has_role(db, "Bootsführer").await
 | 
			
		||||
@@ -440,12 +395,10 @@ ASKÖ Ruderverein Donau Linz", self.name),
 | 
			
		||||
                }
 | 
			
		||||
            } else if self.has_role(db, "Ehrenmitglied").await {
 | 
			
		||||
                fee.add("Ehrenmitglied".into(), 0);
 | 
			
		||||
            } else if halfprice {
 | 
			
		||||
                fee.add("Mitgliedsbeitrag (Halbpreis)".into(), REGULAR / 2);
 | 
			
		||||
            } else {
 | 
			
		||||
                if halfprice {
 | 
			
		||||
                    fee.add("Mitgliedsbeitrag (Halbpreis)".into(), REGULAR / 2);
 | 
			
		||||
                } else {
 | 
			
		||||
                    fee.add("Mitgliedsbeitrag".into(), REGULAR);
 | 
			
		||||
                }
 | 
			
		||||
                fee.add("Mitgliedsbeitrag".into(), REGULAR);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -1039,39 +992,76 @@ ORDER BY last_access DESC
 | 
			
		||||
        smtp_pw: &str,
 | 
			
		||||
    ) {
 | 
			
		||||
        if self.has_role_tx(db, "scheckbuch").await {
 | 
			
		||||
            let amount_trips = Logbook::completed_with_user_tx(db, &self).await.len();
 | 
			
		||||
            if amount_trips == 5 {
 | 
			
		||||
                if let Some(mail) = &self.mail {
 | 
			
		||||
                    let _ = self.send_end_mail_scheckbuch(db, mail, smtp_pw).await;
 | 
			
		||||
            let amount_trips = Logbook::completed_with_user_tx(db, self).await.len();
 | 
			
		||||
            match amount_trips {
 | 
			
		||||
                5 => {
 | 
			
		||||
                    if let Some(mail) = &self.mail {
 | 
			
		||||
                        let _ = self.send_end_mail_scheckbuch(db, mail, smtp_pw).await;
 | 
			
		||||
                    }
 | 
			
		||||
                    Notification::create_for_steering_people_tx(
 | 
			
		||||
                        db,
 | 
			
		||||
                        &format!(
 | 
			
		||||
                            "Liebe Steuerberechtigte, {} hat alle Ausfahrten des Scheckbuchs absolviert. Hoffentlich können wir uns bald über ein neues Mitglied freuen :-)",
 | 
			
		||||
                            self.name
 | 
			
		||||
                        ),
 | 
			
		||||
                        "Scheckbuch fertig",
 | 
			
		||||
                        None,None
 | 
			
		||||
                    )
 | 
			
		||||
                    .await;
 | 
			
		||||
                }
 | 
			
		||||
                Notification::create_for_steering_people_tx(
 | 
			
		||||
            db,
 | 
			
		||||
            &format!(
 | 
			
		||||
                "Liebe Steuerberechtigte, {} hat alle Ausfahrten des Scheckbuchs absolviert. Hoffentlich können wir uns bald über ein neues Mitglied freuen :-)",
 | 
			
		||||
                self.name
 | 
			
		||||
            ),
 | 
			
		||||
            "Scheckbuch fertig",
 | 
			
		||||
            None,None
 | 
			
		||||
        )
 | 
			
		||||
        .await;
 | 
			
		||||
            } else if amount_trips > 5 {
 | 
			
		||||
                let board = Role::find_by_name_tx(db, "Vorstand").await.unwrap();
 | 
			
		||||
                Notification::create_for_role_tx(
 | 
			
		||||
            db,
 | 
			
		||||
            &board,
 | 
			
		||||
            &format!(
 | 
			
		||||
                "Lieber Vorstand, {} hat nun bereits die {}. seiner 5 Scheckbuchausfahrten absolviert.",
 | 
			
		||||
                self.name, amount_trips
 | 
			
		||||
            ),
 | 
			
		||||
            "Scheckbuch überfertig",
 | 
			
		||||
            None,None
 | 
			
		||||
        )
 | 
			
		||||
        .await;
 | 
			
		||||
                a if a > 5 => {
 | 
			
		||||
                    let board = Role::find_by_name_tx(db, "Vorstand").await.unwrap();
 | 
			
		||||
                    Notification::create_for_role_tx(
 | 
			
		||||
                        db,
 | 
			
		||||
                        &board,
 | 
			
		||||
                        &format!(
 | 
			
		||||
                            "Lieber Vorstand, {} hat nun bereits die {}. seiner 5 Scheckbuchausfahrten absolviert.",
 | 
			
		||||
                            self.name, amount_trips
 | 
			
		||||
                        ),
 | 
			
		||||
                        "Scheckbuch überfertig",
 | 
			
		||||
                        None,None
 | 
			
		||||
                    )
 | 
			
		||||
                    .await;
 | 
			
		||||
                }
 | 
			
		||||
                _ => {}
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: check fahrtenabzeichen fertig?
 | 
			
		||||
        // TODO: check äquatorpreis geschafft?
 | 
			
		||||
        // check fahrtenabzeichen fertig
 | 
			
		||||
        if rowingbadge::Status::completed_with_last_log(db, self).await {
 | 
			
		||||
            let board = Role::find_by_name_tx(db, "Vorstand").await.unwrap();
 | 
			
		||||
            Notification::create_for_role_tx(
 | 
			
		||||
            db,
 | 
			
		||||
            &board,
 | 
			
		||||
            &format!(
 | 
			
		||||
                "Lieber Vorstand, zur Info: {} hat gerade alle Anforderungen für das diesjährige Fahrtenabzeichen erfüllt.",
 | 
			
		||||
                self.name
 | 
			
		||||
            ),
 | 
			
		||||
            "Fahrtenabzeichen geschafft",
 | 
			
		||||
            None,None
 | 
			
		||||
        )
 | 
			
		||||
        .await;
 | 
			
		||||
 | 
			
		||||
            Notification::create_with_tx(db, self, "Mit deiner letzten Ausfahrt hast du nun alle Anforderungen für das heurige Fahrtenzeichen erfüllt. Gratuliere! 🎉", "Fahrtenabzeichen geschafft", None, None).await;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check äquatorpreis geschafft?
 | 
			
		||||
        if let Some(level) = equatorprice::new_level_with_last_log(db, self).await {
 | 
			
		||||
            let board = Role::find_by_name_tx(db, "Vorstand").await.unwrap();
 | 
			
		||||
            Notification::create_for_role_tx(
 | 
			
		||||
            db,
 | 
			
		||||
            &board,
 | 
			
		||||
            &format!(
 | 
			
		||||
                "Lieber Vorstand, zur Info: {} hat gerade alle Anforderungen für den Äquatorpreis in {level} geschafft.",
 | 
			
		||||
                self.name
 | 
			
		||||
            ),
 | 
			
		||||
            "Äquatorpreis",
 | 
			
		||||
            None,None
 | 
			
		||||
        )
 | 
			
		||||
        .await;
 | 
			
		||||
 | 
			
		||||
            Notification::create_with_tx(db, self, &format!("Mit deiner letzten Ausfahrt erfüllst du nun alle Anforderungen für den Äquatorpreis in {level}. Gratuliere! 🎉"), "Äquatorpreis", None, None).await;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -408,7 +408,7 @@ async fn create_scheckbuch(
 | 
			
		||||
        format!("{} created new scheckbuch: {data:?}", admin.name),
 | 
			
		||||
    )
 | 
			
		||||
    .await;
 | 
			
		||||
    Flash::success(Redirect::to("/admin/user/scheckbuch"), &format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {mail} verschickt."))
 | 
			
		||||
    Flash::success(Redirect::to("/admin/user/scheckbuch"), format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {mail} verschickt."))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/user/move/schnupperant/<id>/to/scheckbuch")]
 | 
			
		||||
@@ -458,7 +458,7 @@ async fn schnupper_to_scheckbuch(
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    .await;
 | 
			
		||||
    Flash::success(Redirect::to("/admin/schnupper"), &format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {} verschickt.", user.mail.unwrap()))
 | 
			
		||||
    Flash::success(Redirect::to("/admin/schnupper"), format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {} verschickt.", user.mail.unwrap()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn routes() -> Vec<Route> {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,12 +18,12 @@ async fn index(
 | 
			
		||||
        context.insert("flash", &msg.into_inner());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let role = Role::find_by_name(&db, "Donau Linz").await.unwrap();
 | 
			
		||||
    let users = User::all_with_role(&db, &role).await;
 | 
			
		||||
    let role = Role::find_by_name(db, "Donau Linz").await.unwrap();
 | 
			
		||||
    let users = User::all_with_role(db, &role).await;
 | 
			
		||||
    let mut people = Vec::new();
 | 
			
		||||
    let mut rowingbadge_year = None;
 | 
			
		||||
    for user in users {
 | 
			
		||||
        let achievement = Achievements::for_user(&db, &user).await;
 | 
			
		||||
        let achievement = Achievements::for_user(db, &user).await;
 | 
			
		||||
        if let Some(badge) = &achievement.rowingbadge {
 | 
			
		||||
            rowingbadge_year = Some(badge.year);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -148,13 +148,13 @@ async fn fixed<'r>(
 | 
			
		||||
 | 
			
		||||
#[derive(FromForm)]
 | 
			
		||||
pub struct FormBoatDamageVerified<'r> {
 | 
			
		||||
    pub desc: &'r str,
 | 
			
		||||
    desc: &'r str,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[post("/<boatdamage_id>/verified", data = "<data>")]
 | 
			
		||||
async fn verified<'r>(
 | 
			
		||||
    db: &State<SqlitePool>,
 | 
			
		||||
    data: Form<FormBoatDamageFixed<'r>>,
 | 
			
		||||
    data: Form<FormBoatDamageVerified<'r>>,
 | 
			
		||||
    boatdamage_id: i32,
 | 
			
		||||
    techuser: TechUser,
 | 
			
		||||
) -> Flash<Redirect> {
 | 
			
		||||
 
 | 
			
		||||
@@ -217,7 +217,7 @@ async fn new_thirty(
 | 
			
		||||
        eprintln!("Failed to persist file: {:?}", e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let result = data.result.trim_start_matches(|c| c == '0' || c == ' ');
 | 
			
		||||
    let result = data.result.trim_start_matches(['0', ' ']);
 | 
			
		||||
 | 
			
		||||
    sqlx::query!(
 | 
			
		||||
        "UPDATE user SET dirty_thirty = ? where id = ?",
 | 
			
		||||
@@ -318,7 +318,7 @@ async fn new_dozen(
 | 
			
		||||
    if let Err(e) = data.proof.move_copy_to(file_path).await {
 | 
			
		||||
        eprintln!("Failed to persist file: {:?}", e);
 | 
			
		||||
    }
 | 
			
		||||
    let result = data.result.trim_start_matches(|c| c == '0' || c == ' ');
 | 
			
		||||
    let result = data.result.trim_start_matches(['0', ' ']);
 | 
			
		||||
    let result = if result.contains(":") || result.contains(".") {
 | 
			
		||||
        format_time(result)
 | 
			
		||||
    } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -312,7 +312,7 @@ async fn update(
 | 
			
		||||
    let data = data.into_inner();
 | 
			
		||||
 | 
			
		||||
    let Some(logbook) = Logbook::find_by_id(db, data.id).await else {
 | 
			
		||||
        return Flash::error(Redirect::to("/log"), &format!("Logbucheintrag kann nicht bearbeitet werden, da es einen Logbuch-Eintrag mit ID={} nicht gibt", data.id));
 | 
			
		||||
        return Flash::error(Redirect::to("/log"), format!("Logbucheintrag kann nicht bearbeitet werden, da es einen Logbuch-Eintrag mit ID={} nicht gibt", data.id));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match logbook.update(db, data.clone(), &user.user).await {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ async fn cal_registered(
 | 
			
		||||
        return Err("Invalid".into());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if &user.user_token != uuid {
 | 
			
		||||
    if user.user_token != uuid {
 | 
			
		||||
        return Err("Invalid".into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,12 @@
 | 
			
		||||
{% extends "base" %}
 | 
			
		||||
{% macro show_place(aisle_name, side_name, level) %}
 | 
			
		||||
    <li class="truncate p-2 flex relative w-full">
 | 
			
		||||
        {% set aisle = aisle_name ~ "-aisle" %}
 | 
			
		||||
        {% set place = boathouse[aisle][side_name] %}
 | 
			
		||||
        {% set place = boathouse[aisle_name][side_name].boats %}
 | 
			
		||||
        {% if place[level] %}
 | 
			
		||||
            {{ place[level].1.name }}
 | 
			
		||||
            {{ place[level].boat.name }}
 | 
			
		||||
            {% if "admin" in loggedin_user.roles %}
 | 
			
		||||
                <a class="btn btn-primary absolute end-0"
 | 
			
		||||
                   href="/board/boathouse/{{ place[level].0 }}/delete">X</a>
 | 
			
		||||
                   href="/board/boathouse/{{ place[level].boathouse_id }}/delete">X</a>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        {% elif boats | length > 0 %}
 | 
			
		||||
            {% if "admin" in loggedin_user.roles %}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user