forked from Ruderverein-Donau-Linz/rowt
send notifiation to user + vorstand if user completes 'äquatorpreis' or 'fahrtenabzeichen'; Fixes #746
This commit is contained in:
parent
97dd7794fb
commit
d21272d4bb
@ -33,7 +33,7 @@ impl PartialEq for Logbook {
|
||||
|
||||
pub(crate) enum Filter {
|
||||
SingleDayOnly,
|
||||
MultiDazOnly,
|
||||
MultiDayOnly,
|
||||
None,
|
||||
}
|
||||
|
||||
@ -362,12 +362,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<LogbookWithBoatAndRowers> {
|
||||
let logs: Vec<Logbook> = sqlx::query_as(
|
||||
&format!("
|
||||
@ -378,7 +379,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 +390,23 @@ 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);
|
||||
ret.push(LogbookWithBoatAndRowers::from_tx(db, log).await);
|
||||
}
|
||||
}
|
||||
}
|
||||
if exclude_last_log {
|
||||
ret.pop();
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::model::{logbook::Logbook, stat::Stat, user::User};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, PartialEq, Debug)]
|
||||
@ -85,3 +86,19 @@ impl Next {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn new_level_with_last_log(
|
||||
db: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
|
||||
user: &User,
|
||||
) -> Option<String> {
|
||||
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
|
||||
}
|
||||
|
@ -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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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<i32>, 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<i32>,
|
||||
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<i32>, 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)]
|
||||
|
@ -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 {
|
||||
|
@ -1,3 +1,6 @@
|
||||
use super::User;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Fee {
|
||||
pub sum_in_cents: i32,
|
||||
|
@ -15,14 +15,23 @@ 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;
|
||||
|
||||
@ -1016,8 +1025,41 @@ ORDER BY last_access DESC
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user