show if a user has < 30 km to thousand km trip, Fixes #551 #582

Merged
philipp merged 2 commits from thousand-km-trips into main 2024-06-10 15:50:35 +02:00
4 changed files with 114 additions and 9 deletions

View File

@ -301,7 +301,7 @@ ORDER BY departure DESC
db: &SqlitePool, db: &SqlitePool,
mut log: LogToAdd, mut log: LogToAdd,
created_by_user: &User, created_by_user: &User,
) -> Result<(), LogbookCreateError> { ) -> Result<String, LogbookCreateError> {
let Some(boat) = Boat::find_by_id(db, log.boat_id).await else { let Some(boat) = Boat::find_by_id(db, log.boat_id).await else {
return Err(LogbookCreateError::BoatNotFound); return Err(LogbookCreateError::BoatNotFound);
}; };
@ -354,7 +354,7 @@ ORDER BY departure DESC
{ {
Ok(_) => { Ok(_) => {
tx.commit().await.unwrap(); tx.commit().await.unwrap();
Ok(()) Ok(String::new())
} }
Err(a) => Err(a.into()), Err(a) => Err(a.into()),
}; };
@ -426,7 +426,15 @@ ORDER BY departure DESC
tx.commit().await.unwrap(); 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)> { pub async fn distances(db: &SqlitePool) -> Vec<(String, i64)> {
@ -635,6 +643,7 @@ mod test {
use crate::model::user::User; use crate::model::user::User;
use crate::testdb; use crate::testdb;
use chrono::Duration;
use sqlx::SqlitePool; use sqlx::SqlitePool;
#[sqlx::test] #[sqlx::test]
@ -686,7 +695,7 @@ mod test {
fn test_succ_create() { fn test_succ_create() {
let pool = testdb!(); let pool = testdb!();
Logbook::create( let msg = Logbook::create(
&pool, &pool,
LogToAdd { LogToAdd {
boat_id: 3, boat_id: 3,
@ -704,7 +713,62 @@ mod test {
&User::find_by_id(&pool, 4).await.unwrap(), &User::find_by_id(&pool, 4).await.unwrap(),
) )
.await .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] #[sqlx::test]

View File

@ -95,7 +95,7 @@ ORDER BY
#[derive(FromRow, Serialize, Clone)] #[derive(FromRow, Serialize, Clone)]
pub struct Stat { pub struct Stat {
name: String, name: String,
rowed_km: i32, pub(crate) rowed_km: i32,
} }
impl Stat { impl Stat {
@ -195,6 +195,34 @@ ORDER BY rowed_km DESC, u.name;
}) })
.collect() .collect()
} }
pub async fn person(db: &SqlitePool, year: Option<i32>, 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)] #[derive(Debug, Serialize)]
@ -218,7 +246,7 @@ FROM (
LEFT JOIN LEFT JOIN
rower r ON l.id = r.logbook_id rower r ON l.id = r.logbook_id
WHERE WHERE
l.shipmaster = {0} OR r.rower_id = {0} r.rower_id = {}
GROUP BY GROUP BY
departure_date departure_date
) as subquery ) as subquery

View File

@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::{ 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, tripdetails::TripDetails, Day,
}; };
use crate::tera::admin::user::UserEditForm; use crate::tera::admin::user::UserEditForm;
@ -849,6 +849,19 @@ ORDER BY last_access DESC
6 6
} }
} }
pub(crate) async fn close_thousands_trip(&self, db: &SqlitePool) -> Option<String> {
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] #[async_trait]

View File

@ -212,7 +212,7 @@ async fn create_logbook(
) )
.await .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::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::RowerAlreadyOnWater(rower)) => Flash::error(Redirect::to("/log"), format!("Ruderer {} schon am Wasser", rower.name)),
Err(LogbookCreateError::BoatLocked) => Flash::error(Redirect::to("/log"),"Boot gesperrt"), Err(LogbookCreateError::BoatLocked) => Flash::error(Redirect::to("/log"),"Boot gesperrt"),