diff --git a/src/model/logbook.rs b/src/model/logbook.rs index 20585ef..4f9fd1c 100644 --- a/src/model/logbook.rs +++ b/src/model/logbook.rs @@ -301,7 +301,7 @@ ORDER BY departure DESC db: &SqlitePool, mut log: LogToAdd, created_by_user: &User, - ) -> Result<(), LogbookCreateError> { + ) -> Result { let Some(boat) = Boat::find_by_id(db, log.boat_id).await else { return Err(LogbookCreateError::BoatNotFound); }; @@ -354,7 +354,7 @@ ORDER BY departure DESC { Ok(_) => { tx.commit().await.unwrap(); - Ok(()) + Ok(String::new()) } Err(a) => Err(a.into()), }; @@ -426,7 +426,15 @@ ORDER BY departure DESC tx.commit().await.unwrap(); - Ok(()) + let mut ret = String::new(); + for rower in &log.rowers { + let user = User::find_by_id(db, *rower as i32).await.unwrap(); + if let Some(msg) = user.close_thousands_trip(db).await { + ret.push_str(&format!(" • {msg}")); + } + } + + Ok(ret) } pub async fn distances(db: &SqlitePool) -> Vec<(String, i64)> { @@ -635,6 +643,7 @@ mod test { use crate::model::user::User; use crate::testdb; + use chrono::Duration; use sqlx::SqlitePool; #[sqlx::test] @@ -686,7 +695,7 @@ mod test { fn test_succ_create() { let pool = testdb!(); - Logbook::create( + let msg = Logbook::create( &pool, LogToAdd { boat_id: 3, @@ -704,7 +713,62 @@ mod test { &User::find_by_id(&pool, 4).await.unwrap(), ) .await - .unwrap() + .unwrap(); + assert_eq!(msg, String::from("")); + } + + #[sqlx::test] + fn test_succ_create_with_thousands_msg() { + let pool = testdb!(); + + let logbook = Logbook::find_by_id(&pool, 1).await.unwrap(); + let user = User::find_by_id(&pool, 2).await.unwrap(); + let current_date = chrono::Local::now().format("%Y-%m-%d").to_string(); + let start_date = chrono::Local::now() - Duration::days(3); + let start_date = start_date.format("%Y-%m-%d").to_string(); + logbook + .home( + &pool, + &user, + super::LogToFinalize { + destination: "new-destination".into(), + distance_in_km: 995, + comments: Some("Perfect water".into()), + logtype: None, + rowers: vec![2], + shipmaster: Some(2), + steering_person: Some(2), + shipmaster_only_steering: false, + departure: format!("{}T10:00", start_date), + arrival: format!("{}T12:00", current_date), + }, + ) + .await + .unwrap(); + + let msg = Logbook::create( + &pool, + LogToAdd { + boat_id: 3, + shipmaster: Some(2), + steering_person: Some(2), + shipmaster_only_steering: false, + departure: "2128-05-20T12:00".into(), + arrival: None, + destination: None, + distance_in_km: None, + comments: None, + logtype: None, + rowers: vec![2], + }, + &User::find_by_id(&pool, 1).await.unwrap(), + ) + .await + .unwrap(); + assert_eq!( + msg, + String::from(" • rower braucht nur mehr 5 km bis die 1000 km voll sind 🤑") + ); } #[sqlx::test] diff --git a/src/model/stat.rs b/src/model/stat.rs index 0398115..c27a7dd 100644 --- a/src/model/stat.rs +++ b/src/model/stat.rs @@ -95,7 +95,7 @@ ORDER BY #[derive(FromRow, Serialize, Clone)] pub struct Stat { name: String, - rowed_km: i32, + pub(crate) rowed_km: i32, } impl Stat { @@ -195,6 +195,34 @@ ORDER BY rowed_km DESC, u.name; }) .collect() } + pub async fn person(db: &SqlitePool, year: Option, user: &User) -> Stat { + let year = match year { + Some(year) => year, + None => chrono::Local::now().year(), + }; + //TODO: switch to query! macro again (once upgraded to sqlite 3.42 on server) + let row = sqlx::query(&format!( + " +SELECT u.name, CAST(SUM(l.distance_in_km) AS INTEGER) AS rowed_km +FROM ( + SELECT * FROM user + WHERE id={} +) u +INNER JOIN rower r ON u.id = r.rower_id +INNER JOIN logbook l ON r.logbook_id = l.id +WHERE l.distance_in_km IS NOT NULL AND l.arrival LIKE '{year}-%'; +", + user.id + )) + .fetch_one(db) + .await + .unwrap(); + + Stat { + name: row.get("name"), + rowed_km: row.get("rowed_km"), + } + } } #[derive(Debug, Serialize)] @@ -218,7 +246,7 @@ FROM ( LEFT JOIN rower r ON l.id = r.logbook_id WHERE - l.shipmaster = {0} OR r.rower_id = {0} + r.rower_id = {} GROUP BY departure_date ) as subquery diff --git a/src/model/user.rs b/src/model/user.rs index 0c184ec..06fb212 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use super::{ - family::Family, log::Log, mail::Mail, notification::Notification, role::Role, + family::Family, log::Log, mail::Mail, notification::Notification, role::Role, stat::Stat, tripdetails::TripDetails, Day, }; use crate::tera::admin::user::UserEditForm; @@ -849,6 +849,19 @@ ORDER BY last_access DESC 6 } } + + pub(crate) async fn close_thousands_trip(&self, db: &SqlitePool) -> Option { + let rowed_km = Stat::person(db, None, self).await.rowed_km; + if rowed_km % 1000 > 970 { + return Some(format!( + "{} braucht nur mehr {} km bis die {} km voll sind 🤑", + self.name, + 1000 - rowed_km % 1000, + rowed_km + 1000 - (rowed_km % 1000) + )); + } + None + } } #[async_trait] diff --git a/src/tera/log.rs b/src/tera/log.rs index 8fe3131..cf07751 100644 --- a/src/tera/log.rs +++ b/src/tera/log.rs @@ -212,7 +212,7 @@ async fn create_logbook( ) .await { - Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt erfolgreich hinzugefügt"), + Ok(msg) => Flash::success(Redirect::to("/log"), format!("Ausfahrt erfolgreich hinzugefügt{msg}")), Err(LogbookCreateError::BoatAlreadyOnWater) => Flash::error(Redirect::to("/log"), "Boot schon am Wasser"), Err(LogbookCreateError::RowerAlreadyOnWater(rower)) => Flash::error(Redirect::to("/log"), format!("Ruderer {} schon am Wasser", rower.name)), Err(LogbookCreateError::BoatLocked) => Flash::error(Redirect::to("/log"),"Boot gesperrt"), diff --git a/templates/includes/forms/log.html.tera b/templates/includes/forms/log.html.tera index 9bd1c57..e444899 100644 --- a/templates/includes/forms/log.html.tera +++ b/templates/includes/forms/log.html.tera @@ -279,4 +279,8 @@ + Löschen {% endmacro home %}