add scheckbook functionality, Fixes #184 (#235)
All checks were successful
CI/CD Pipeline / test (push) Successful in 19m38s
CI/CD Pipeline / deploy-staging (push) Successful in 4m17s
CI/CD Pipeline / deploy-main (push) Has been skipped

Reviewed-on: #235
This commit is contained in:
2024-03-04 23:11:44 +01:00
parent c813e2b3da
commit 007b87e8ad
9 changed files with 182 additions and 34 deletions

View File

@ -226,6 +226,38 @@ ORDER BY departure DESC
ret
}
pub async fn completed_with_user(
db: &SqlitePool,
user: &User,
) -> Vec<LogbookWithBoatAndRowers> {
let logs = sqlx::query_as(
&format!("
SELECT id, boat_id, shipmaster, steering_person, shipmaster_only_steering, departure, arrival, destination, distance_in_km, comments, logtype
FROM logbook
JOIN rower ON logbook.id = rower.logbook_id
WHERE arrival is not null AND rower_id = {}
ORDER BY departure DESC
", user.id)
)
.fetch_all(db)
.await
.unwrap(); //TODO: fixme
let mut ret = Vec::new();
for log in logs {
ret.push(LogbookWithBoatAndRowers {
rowers: Rower::for_log(db, &log).await,
boat: Boat::find_by_id(db, log.boat_id as i32).await.unwrap(),
shipmaster_user: User::find_by_id(db, log.shipmaster as i32).await.unwrap(),
steering_user: User::find_by_id(db, log.steering_person as i32)
.await
.unwrap(),
logbook: log,
});
}
ret
}
pub async fn completed(db: &SqlitePool) -> Vec<LogbookWithBoatAndRowers> {
let year = chrono::Utc::now().year();
let logs = sqlx::query_as(

View File

@ -225,28 +225,6 @@ impl User {
.count
}
pub async fn rowed_km(&self, db: &SqlitePool) -> i32 {
sqlx::query!(
"SELECT COALESCE(SUM(distance_in_km),0) as rowed_km
FROM (
SELECT distance_in_km
FROM logbook
WHERE shipmaster = ?1
UNION
SELECT l.distance_in_km
FROM logbook l
INNER JOIN rower r ON r.logbook_id = l.id
WHERE r.rower_id = ?1
);",
self.id,
)
.fetch_one(db)
.await
.unwrap()
.rowed_km
}
pub async fn has_role(&self, db: &SqlitePool, role: &str) -> bool {
if sqlx::query!(
"SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)",
@ -379,6 +357,22 @@ ORDER BY last_access DESC
.unwrap()
}
pub async fn all_with_role(db: &SqlitePool, role: &Role) -> Vec<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id
FROM user u
JOIN user_role ur ON u.id = ur.user_id
WHERE ur.role_id = ? AND deleted = 0
ORDER BY name;
", role.id
)
.fetch_all(db)
.await
.unwrap()
}
pub async fn all_payer_groups(db: &SqlitePool) -> Vec<Self> {
sqlx::query_as!(
Self,

View File

@ -2,20 +2,38 @@ use std::collections::HashMap;
use crate::model::{
family::Family,
logbook::Logbook,
role::Role,
user::{AdminUser, User, UserWithRoles, VorstandUser},
};
use futures::future::join_all;
use rocket::{
form::Form,
get, post,
request::FlashMessage,
get,
http::Status,
post,
request::{FlashMessage, FromRequest, Outcome},
response::{Flash, Redirect},
routes, FromForm, Route, State,
routes, FromForm, Request, Route, State,
};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;
// Custom request guard to extract the Referer header
struct Referer(String);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Referer {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
match request.headers().get_one("Referer") {
Some(referer) => Outcome::Success(Referer(referer.to_string())),
None => Outcome::Error((Status::BadRequest, ())),
}
}
}
#[get("/user")]
async fn index(
db: &State<SqlitePool>,
@ -111,11 +129,43 @@ async fn fees(
Template::render("admin/user/fees", context.into_json())
}
#[get("/user/scheckbuch")]
async fn scheckbuch(
db: &State<SqlitePool>,
user: VorstandUser,
flash: Option<FlashMessage<'_>>,
) -> Template {
let mut context = Context::new();
let scheckbooks = Role::find_by_name(db, "scheckbuch").await.unwrap();
let scheckbooks = User::all_with_role(db, &scheckbooks).await;
let mut scheckbooks_with_roles = Vec::new();
for s in scheckbooks {
scheckbooks_with_roles.push((
Logbook::completed_with_user(db, &s).await,
UserWithRoles::from_user(s, db).await,
))
}
context.insert("scheckbooks", &scheckbooks_with_roles);
if let Some(msg) = flash {
context.insert("flash", &msg.into_inner());
}
context.insert(
"loggedin_user",
&UserWithRoles::from_user(user.into(), db).await,
);
Template::render("admin/user/scheckbuch", context.into_json())
}
#[get("/user/fees/paid?<user_ids>")]
async fn fees_paid(
db: &State<SqlitePool>,
_admin: AdminUser,
user_ids: Vec<i32>,
referer: Referer,
) -> Flash<Redirect> {
let mut res = String::new();
for user_id in user_ids {
@ -133,7 +183,7 @@ async fn fees_paid(
res.truncate(res.len() - 3); // remove ' + ' from the end
Flash::success(
Redirect::to("/admin/user/fees"),
Redirect::to(referer.0),
format!("Zahlungsstatus von {} erfolgreich geändert", res),
)
}
@ -234,6 +284,7 @@ pub fn routes() -> Vec<Route> {
create,
delete,
fees,
fees_paid
fees_paid,
scheckbuch
]
}

View File

@ -10,6 +10,7 @@ use tera::Context;
use crate::model::{
log::Log,
logbook::Logbook,
tripdetails::TripDetails,
triptype::TripType,
user::{AllowedForPlannedTripsUser, User, UserWithRoles},
@ -31,6 +32,11 @@ async fn index(
context.insert("trip_types", &triptypes);
}
if user.has_role(db, "scheckbuch").await {
let last_trips = Logbook::completed_with_user(db, &user).await;
context.insert("last_trips", &last_trips);
}
let days = user.get_days(db).await;
if let Some(msg) = flash {