membership-pdf-new #284
| @@ -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( | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
|     ] | ||||
| } | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -5,9 +5,7 @@ | ||||
|     <div class="max-w-screen-lg w-full"> | ||||
|         <h1 class="h1">List - Result</h1> | ||||
|         <ol> | ||||
| 	{% for person in result%} | ||||
| 		<li>{{person}}</li> | ||||
| 	{% endfor %} | ||||
|             {% for person in result %}<li>{{ person }}</li>{% endfor %} | ||||
|         </ol> | ||||
|     </div> | ||||
| {% 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 %} | ||||
|         <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 -}} | ||||
|                 {% if log.shipmaster_only_steering %}- handgesteuert{% endif -%} | ||||
|                 {% if log.shipmaster_only_steering %} | ||||
|                     - handgesteuert | ||||
|                 {% endif -%} | ||||
|             )</small> | ||||
|             <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') %} | ||||
|   | ||||
| @@ -89,6 +89,9 @@ | ||||
|                             <li class="py-1"> | ||||
|                                 <a href="/admin/user/fees" class="link-primary">Übersicht User Gebühren</a> | ||||
|                             </li> | ||||
|                             <li class="py-1"> | ||||
|                                 <a href="/admin/user/scheckbuch" class="link-primary">Scheckbuch</a> | ||||
|                             </li> | ||||
|                             <li class="py-1"> | ||||
|                                 <a href="/admin/user" class="link-primary">User</a> | ||||
|                             </li> | ||||
|   | ||||
| @@ -1,9 +1,26 @@ | ||||
| {% import "includes/macros" as macros %} | ||||
| {% import "includes/forms/log" as log %} | ||||
| {% extends "base" %} | ||||
| {% block content %} | ||||
|     <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 "paid" not in loggedin_user.roles %} | ||||
|         {% 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 %} | ||||
|             <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"> | ||||
| @@ -238,7 +255,9 @@ | ||||
|                                                 <strong class="text-primary-900 dark:text-white">{{ trip.planned_starting_time }} | ||||
|                                                 Uhr</strong> | ||||
|                                                 <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> | ||||
|                                             {% endif %} | ||||
|                                             <br /> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user