diff --git a/src/model/boathouse.rs b/src/model/boathouse.rs index 0e96caf..03d0f78 100644 --- a/src/model/boathouse.rs +++ b/src/model/boathouse.rs @@ -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; 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) -> 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> { diff --git a/src/model/logbook.rs b/src/model/logbook.rs index a85ab46..4b104bf 100644 --- a/src/model/logbook.rs +++ b/src/model/logbook.rs @@ -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 { let logs: Vec = 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 } diff --git a/src/model/personal/equatorprice.rs b/src/model/personal/equatorprice.rs index 97a489a..2568987 100644 --- a/src/model/personal/equatorprice.rs +++ b/src/model/personal/equatorprice.rs @@ -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 { + 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 +} diff --git a/src/model/personal/rowingbadge.rs b/src/model/personal/rowingbadge.rs index 446b886..93a969a 100644 --- a/src/model/personal/rowingbadge.rs +++ b/src/model/personal/rowingbadge.rs @@ -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 { + 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 { 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 { + 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 + } } diff --git a/src/model/stat.rs b/src/model/stat.rs index 6ec6b16..2ed663a 100644 --- a/src/model/stat.rs +++ b/src/model/stat.rs @@ -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, 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, + 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, 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)] diff --git a/src/model/trip.rs b/src/model/trip.rs index 71d98df..b8178fa 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -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 { diff --git a/src/model/user/fee.rs b/src/model/user/fee.rs new file mode 100644 index 0000000..1fa9841 --- /dev/null +++ b/src/model/user/fee.rs @@ -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, +} + +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); + } + } +} diff --git a/src/model/user.rs b/src/model/user/mod.rs similarity index 93% rename from src/model/user.rs rename to src/model/user/mod.rs index 3c544d7..a72ed3b 100644 --- a/src/model/user.rs +++ b/src/model/user/mod.rs @@ -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, -} - -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; + } } } diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index 3c7564d..8cea3d8 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -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//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 { diff --git a/src/tera/board/achievement.rs b/src/tera/board/achievement.rs index 0ac17c3..750ab25 100644 --- a/src/tera/board/achievement.rs +++ b/src/tera/board/achievement.rs @@ -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); } diff --git a/src/tera/boatdamage.rs b/src/tera/boatdamage.rs index ec80954..2e345ac 100644 --- a/src/tera/boatdamage.rs +++ b/src/tera/boatdamage.rs @@ -148,13 +148,13 @@ async fn fixed<'r>( #[derive(FromForm)] pub struct FormBoatDamageVerified<'r> { - pub desc: &'r str, + desc: &'r str, } #[post("//verified", data = "")] async fn verified<'r>( db: &State, - data: Form>, + data: Form>, boatdamage_id: i32, techuser: TechUser, ) -> Flash { diff --git a/src/tera/ergo.rs b/src/tera/ergo.rs index e3c4f1f..31b3d0d 100644 --- a/src/tera/ergo.rs +++ b/src/tera/ergo.rs @@ -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 { diff --git a/src/tera/log.rs b/src/tera/log.rs index e9f3746..ae82c3b 100644 --- a/src/tera/log.rs +++ b/src/tera/log.rs @@ -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 { diff --git a/src/tera/misc.rs b/src/tera/misc.rs index 4d0e76e..929694e 100644 --- a/src/tera/misc.rs +++ b/src/tera/misc.rs @@ -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()); } diff --git a/templates/board/boathouse.html.tera b/templates/board/boathouse.html.tera index 29e5665..9223dd3 100644 --- a/templates/board/boathouse.html.tera +++ b/templates/board/boathouse.html.tera @@ -4,13 +4,12 @@ {% extends "base" %} {% macro show_place(aisle_name, side_name, level) %}
  • - {% 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 %} X + href="/board/boathouse/{{ place[level].boathouse_id }}/delete">X {% endif %} {% elif boats | length > 0 %} {% if "admin" in loggedin_user.roles %}