From 31bf38f11271b8e81a7ed560217929660af69c2f Mon Sep 17 00:00:00 2001 From: philipp Date: Mon, 10 Jun 2024 14:55:01 +0200 Subject: [PATCH 1/2] show if a user has < 30 km to thousand km trip, Fixes #551 --- src/model/logbook.rs | 14 +++++++++++--- src/model/stat.rs | 32 ++++++++++++++++++++++++++++++-- src/model/user.rs | 15 ++++++++++++++- src/tera/log.rs | 2 +- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/model/logbook.rs b/src/model/logbook.rs index 20585ef..e2d7f05 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!("\n{msg}")); + } + } + + Ok(ret) } pub async fn distances(db: &SqlitePool) -> Vec<(String, i64)> { 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..7c7ab0c 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"), From dea0c65da37c983f7250dc0cd171b19fd80cd093 Mon Sep 17 00:00:00 2001 From: philipp Date: Mon, 10 Jun 2024 15:12:23 +0200 Subject: [PATCH 2/2] fix ci + add test --- src/model/logbook.rs | 62 +++++++++++++++++++++++++++++++++++++++++--- src/tera/log.rs | 2 +- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/model/logbook.rs b/src/model/logbook.rs index e2d7f05..4f9fd1c 100644 --- a/src/model/logbook.rs +++ b/src/model/logbook.rs @@ -430,7 +430,7 @@ ORDER BY departure DESC 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!("\n{msg}")); + ret.push_str(&format!(" • {msg}")); } } @@ -643,6 +643,7 @@ mod test { use crate::model::user::User; use crate::testdb; + use chrono::Duration; use sqlx::SqlitePool; #[sqlx::test] @@ -694,7 +695,7 @@ mod test { fn test_succ_create() { let pool = testdb!(); - Logbook::create( + let msg = Logbook::create( &pool, LogToAdd { boat_id: 3, @@ -712,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/tera/log.rs b/src/tera/log.rs index 7c7ab0c..cf07751 100644 --- a/src/tera/log.rs +++ b/src/tera/log.rs @@ -212,7 +212,7 @@ async fn create_logbook( ) .await { - Ok(msg) => Flash::success(Redirect::to("/log"), format!("Ausfahrt erfolgreich hinzugefügt {msg}")), + 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"),