thousand-km-trips #581

Merged
philipp merged 9 commits from thousand-km-trips into staging 2024-06-10 15:12:58 +02:00
6 changed files with 126 additions and 17 deletions

View File

@ -1,3 +1,5 @@
![latest CI run on main](https://git.hofer.link/Ruderverein-Donau-Linz/rowt/actions/workflows/action.yml/badge.svg?branch=main)
# Build
## Frontend
1. `cd frontend`

View File

@ -360,21 +360,23 @@ WHERE trip_details.id=?
event.day.replace('-', ""),
event.planned_starting_time.replace(':', "")
)));
let tripdetails = event.trip_details(db).await;
let mut name = String::new();
if event.is_cancelled() {
name.push_str("ABGESAGT! :-( ");
name.push_str("ABGESAGT");
if let Some(notes) = &tripdetails.notes {
if !notes.is_empty() {
name.push_str(&format!(" (Grund: {notes})"))
}
}
name.push_str("! :-( ");
}
name.push_str(&format!("{} ", event.name));
let tripdetails = event.trip_details(db).await;
if let Some(triptype) = tripdetails.triptype(db).await {
name.push_str(&format!("{} ", triptype.name))
}
if let Some(notes) = tripdetails.notes {
if !notes.is_empty() {
name.push_str(&format!("({notes}) "))
}
}
vevent.push(Summary::new(name));
calendar.add_event(vevent);
}
@ -434,6 +436,6 @@ mod test {
let pool = testdb!();
let actual = Event::get_ics_feed(&pool).await;
assert_eq!("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-rs\r\nBEGIN:VEVENT\r\nUID:1@rudernlinz.at\r\nDTSTAMP:19900101T180000\r\nDTSTART:19700101T100000\r\nSUMMARY:test-planned-event (trip_details for a planned event) \r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", actual);
assert_eq!("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-rs\r\nBEGIN:VEVENT\r\nUID:1@rudernlinz.at\r\nDTSTAMP:19900101T180000\r\nDTSTART:19700101T100000\r\nSUMMARY:test-planned-event \r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", actual);
}
}

View File

@ -301,7 +301,7 @@ ORDER BY departure DESC
db: &SqlitePool,
mut log: LogToAdd,
created_by_user: &User,
) -> Result<(), LogbookCreateError> {
) -> Result<String, LogbookCreateError> {
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]

View File

@ -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<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)]
@ -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

View File

@ -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<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]

View File

@ -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"),