add scheckbook functionality, Fixes #184 #235
@ -226,6 +226,38 @@ ORDER BY departure DESC
|
|||||||
ret
|
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> {
|
pub async fn completed(db: &SqlitePool) -> Vec<LogbookWithBoatAndRowers> {
|
||||||
let year = chrono::Utc::now().year();
|
let year = chrono::Utc::now().year();
|
||||||
let logs = sqlx::query_as(
|
let logs = sqlx::query_as(
|
||||||
|
@ -225,28 +225,6 @@ impl User {
|
|||||||
.count
|
.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 {
|
pub async fn has_role(&self, db: &SqlitePool, role: &str) -> bool {
|
||||||
if sqlx::query!(
|
if sqlx::query!(
|
||||||
"SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)",
|
"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()
|
.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> {
|
pub async fn all_payer_groups(db: &SqlitePool) -> Vec<Self> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
|
@ -2,20 +2,38 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
family::Family,
|
family::Family,
|
||||||
|
logbook::Logbook,
|
||||||
role::Role,
|
role::Role,
|
||||||
user::{AdminUser, User, UserWithRoles, VorstandUser},
|
user::{AdminUser, User, UserWithRoles, VorstandUser},
|
||||||
};
|
};
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
form::Form,
|
form::Form,
|
||||||
get, post,
|
get,
|
||||||
request::FlashMessage,
|
http::Status,
|
||||||
|
post,
|
||||||
|
request::{FlashMessage, FromRequest, Outcome},
|
||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
routes, FromForm, Route, State,
|
routes, FromForm, Request, Route, State,
|
||||||
};
|
};
|
||||||
use rocket_dyn_templates::{tera::Context, Template};
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
use sqlx::SqlitePool;
|
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")]
|
#[get("/user")]
|
||||||
async fn index(
|
async fn index(
|
||||||
db: &State<SqlitePool>,
|
db: &State<SqlitePool>,
|
||||||
@ -111,11 +129,43 @@ async fn fees(
|
|||||||
Template::render("admin/user/fees", context.into_json())
|
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>")]
|
#[get("/user/fees/paid?<user_ids>")]
|
||||||
async fn fees_paid(
|
async fn fees_paid(
|
||||||
db: &State<SqlitePool>,
|
db: &State<SqlitePool>,
|
||||||
_admin: AdminUser,
|
_admin: AdminUser,
|
||||||
user_ids: Vec<i32>,
|
user_ids: Vec<i32>,
|
||||||
|
referer: Referer,
|
||||||
) -> Flash<Redirect> {
|
) -> Flash<Redirect> {
|
||||||
let mut res = String::new();
|
let mut res = String::new();
|
||||||
for user_id in user_ids {
|
for user_id in user_ids {
|
||||||
@ -133,7 +183,7 @@ async fn fees_paid(
|
|||||||
res.truncate(res.len() - 3); // remove ' + ' from the end
|
res.truncate(res.len() - 3); // remove ' + ' from the end
|
||||||
|
|
||||||
Flash::success(
|
Flash::success(
|
||||||
Redirect::to("/admin/user/fees"),
|
Redirect::to(referer.0),
|
||||||
format!("Zahlungsstatus von {} erfolgreich geändert", res),
|
format!("Zahlungsstatus von {} erfolgreich geändert", res),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -234,6 +284,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
create,
|
create,
|
||||||
delete,
|
delete,
|
||||||
fees,
|
fees,
|
||||||
fees_paid
|
fees_paid,
|
||||||
|
scheckbuch
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use tera::Context;
|
|||||||
|
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
log::Log,
|
log::Log,
|
||||||
|
logbook::Logbook,
|
||||||
tripdetails::TripDetails,
|
tripdetails::TripDetails,
|
||||||
triptype::TripType,
|
triptype::TripType,
|
||||||
user::{AllowedForPlannedTripsUser, User, UserWithRoles},
|
user::{AllowedForPlannedTripsUser, User, UserWithRoles},
|
||||||
@ -31,6 +32,11 @@ async fn index(
|
|||||||
context.insert("trip_types", &triptypes);
|
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;
|
let days = user.get_days(db).await;
|
||||||
|
|
||||||
if let Some(msg) = flash {
|
if let Some(msg) = flash {
|
||||||
|
@ -5,9 +5,7 @@
|
|||||||
<div class="max-w-screen-lg w-full">
|
<div class="max-w-screen-lg w-full">
|
||||||
<h1 class="h1">List - Result</h1>
|
<h1 class="h1">List - Result</h1>
|
||||||
<ol>
|
<ol>
|
||||||
{% for person in result%}
|
{% for person in result %}<li>{{ person }}</li>{% endfor %}
|
||||||
<li>{{person}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
43
templates/admin/user/scheckbuch.html.tera
Normal file
43
templates/admin/user/scheckbuch.html.tera
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{% import "includes/macros" as macros %}
|
||||||
|
{% import "includes/forms/log" as log %}
|
||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="max-w-screen-lg w-full bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
|
||||||
|
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
|
||||||
|
<h1 class="h1">Scheckbücher</h1>
|
||||||
|
<!-- START filterBar -->
|
||||||
|
<div class="search-wrapper">
|
||||||
|
<label for="name" class="sr-only">Suche</label>
|
||||||
|
<input type="search"
|
||||||
|
name="name"
|
||||||
|
id="filter-js"
|
||||||
|
class="search-bar"
|
||||||
|
placeholder="Suchen nach Name" />
|
||||||
|
</div>
|
||||||
|
<!-- END filterBar -->
|
||||||
|
<div class="bg-primary-100 dark:bg-primary-950 p-3 rounded-b-md grid gap-4">
|
||||||
|
<div id="filter-result-js"
|
||||||
|
class="text-primary-950 dark:text-white text-right"></div>
|
||||||
|
{% for scheckbook in scheckbooks %}
|
||||||
|
{% set user = scheckbook.1 %}
|
||||||
|
{% set trips = scheckbook.0 %}
|
||||||
|
<div {% if "paid" in user.roles %}style="background-color: green;"{% endif %}
|
||||||
|
data-filterable="true"
|
||||||
|
data-filter="{{ user.name }} {% if "paid" in user.roles %} has-already-paid {% else %} has-not-paid {% endif %}"
|
||||||
|
class="bg-white dark:bg-primary-900 p-3 rounded-md w-full">
|
||||||
|
<div class="grid sm:grid-cols-1 gap-3">
|
||||||
|
<div style="width: 100%" class="col-span-2">
|
||||||
|
<b>{{ user.name }} - {{ trips | length }} Ausfahrten</b>
|
||||||
|
{% for trip in trips %}
|
||||||
|
<li>{{ log::show_old(log=trip, state="completed", only_ones=false, index=loop.index) }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if "admin" in loggedin_user.roles %}
|
||||||
|
<a href="/admin/user/fees/paid?user_ids[]={{ user.id }}">Zahlungsstatus ändern</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
@ -181,7 +181,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
|
<div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
|
||||||
<strong class="text-black dark:text-white">{{ log.boat.name }}</strong> <small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
|
<strong class="text-black dark:text-white">{{ log.boat.name }}</strong> <small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
|
||||||
{% if log.shipmaster_only_steering %}- handgesteuert{% endif -%}
|
{% if log.shipmaster_only_steering %}
|
||||||
|
- handgesteuert
|
||||||
|
{% endif -%}
|
||||||
)</small>
|
)</small>
|
||||||
<small class="block text-gray-600 dark:text-gray-100">
|
<small class="block text-gray-600 dark:text-gray-100">
|
||||||
{% if state == "completed" and log.departure | date(format='%d.%m.%Y') == log.arrival | date(format='%d.%m.%Y') %}
|
{% if state == "completed" and log.departure | date(format='%d.%m.%Y') == log.arrival | date(format='%d.%m.%Y') %}
|
||||||
|
@ -89,6 +89,9 @@
|
|||||||
<li class="py-1">
|
<li class="py-1">
|
||||||
<a href="/admin/user/fees" class="link-primary">Übersicht User Gebühren</a>
|
<a href="/admin/user/fees" class="link-primary">Übersicht User Gebühren</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="py-1">
|
||||||
|
<a href="/admin/user/scheckbuch" class="link-primary">Scheckbuch</a>
|
||||||
|
</li>
|
||||||
<li class="py-1">
|
<li class="py-1">
|
||||||
<a href="/admin/user" class="link-primary">User</a>
|
<a href="/admin/user" class="link-primary">User</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,8 +1,25 @@
|
|||||||
{% import "includes/macros" as macros %}
|
{% import "includes/macros" as macros %}
|
||||||
|
{% import "includes/forms/log" as log %}
|
||||||
{% extends "base" %}
|
{% extends "base" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-screen-xl w-full grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div class="max-w-screen-xl w-full grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
|
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
|
||||||
|
{% if "scheckbuch" in loggedin_user.roles %}
|
||||||
|
<div class="grid gap-3 sm:col-span-2 lg:col-span-3">
|
||||||
|
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
|
||||||
|
role="alert">
|
||||||
|
<h2 class="h2">Scheckbuch</h2>
|
||||||
|
<div class="text-sm p-3">
|
||||||
|
<h3>Du hast bisher {{ last_trips | length }} deiner 5 Scheckbuch-Ausfahrten gemacht:</h3>
|
||||||
|
<ol>
|
||||||
|
{% for last_trip in last_trips %}
|
||||||
|
<li>{{ log::show_old(log=last_trip, state="completed", only_ones=false, index=loop.index) }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if "paid" not in loggedin_user.roles and "Donau Linz" in loggedin_user.roles %}
|
{% if "paid" not in loggedin_user.roles and "Donau Linz" in loggedin_user.roles %}
|
||||||
<div class="grid gap-3 sm:col-span-2 lg:col-span-3">
|
<div class="grid gap-3 sm:col-span-2 lg:col-span-3">
|
||||||
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
|
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
|
||||||
@ -238,7 +255,9 @@
|
|||||||
<strong class="text-primary-900 dark:text-white">{{ trip.planned_starting_time }}
|
<strong class="text-primary-900 dark:text-white">{{ trip.planned_starting_time }}
|
||||||
Uhr</strong>
|
Uhr</strong>
|
||||||
<small class="text-gray-600 dark:text-gray-100">({{ trip.cox_name -}}
|
<small class="text-gray-600 dark:text-gray-100">({{ trip.cox_name -}}
|
||||||
{% if trip.trip_type %}- {{ trip.trip_type.icon | safe }} {{ trip.trip_type.name }}{% endif -%}
|
{% if trip.trip_type %}
|
||||||
|
- {{ trip.trip_type.icon | safe }} {{ trip.trip_type.name }}
|
||||||
|
{% endif -%}
|
||||||
)</small>
|
)</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br />
|
<br />
|
||||||
|
Loading…
Reference in New Issue
Block a user