Merge pull request 'single-user-edit-page' (#971) from single-user-edit-page into staging
Reviewed-on: #971
This commit was merged in pull request #971.
	This commit is contained in:
		| @@ -5,7 +5,8 @@ | ||||
| .input-group { | ||||
|   @apply flex; | ||||
|  | ||||
|   input[readonly] { | ||||
|   input[readonly],  | ||||
|   select[disabled] { | ||||
|     opacity: .7; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -10,4 +10,8 @@ | ||||
|   &-white { | ||||
|     @apply text-white hover:text-primary-100 underline; | ||||
|   } | ||||
|  | ||||
|   &-no-underline { | ||||
|     @apply no-underline; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -225,6 +225,15 @@ CREATE TABLE IF NOT EXISTS "distance" ( | ||||
| ); | ||||
|  | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS "activity" ( | ||||
|     id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||
|     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||
|     text TEXT NOT NULL, | ||||
|     relevant_for TEXT NOT NULL,  -- e.g. user_id=123;trip_id=456 | ||||
|     keep_until DATETIME | ||||
| ); | ||||
|  | ||||
|  | ||||
| CREATE TRIGGER IF NOT EXISTS prevent_multiple_roles_same_cluster | ||||
| BEFORE INSERT ON user_role | ||||
| BEGIN | ||||
|   | ||||
							
								
								
									
										54
									
								
								src/model/activity.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/model/activity.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| use std::ops::DerefMut; | ||||
|  | ||||
| use chrono::NaiveDateTime; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; | ||||
|  | ||||
| #[derive(FromRow, Debug, Serialize, Deserialize, Clone)] | ||||
| pub struct Activity { | ||||
|     pub id: i64, | ||||
|     pub created_at: NaiveDateTime, | ||||
|     pub text: String, | ||||
|     pub relevant_for: String, | ||||
|     pub keep_until: Option<NaiveDateTime>, | ||||
| } | ||||
|  | ||||
| impl Activity { | ||||
|     pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> { | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             "SELECT id, created_at, text, relevant_for, keep_until FROM activity WHERE id like ?", | ||||
|             id | ||||
|         ) | ||||
|         .fetch_one(db) | ||||
|         .await | ||||
|         .ok() | ||||
|     } | ||||
|     pub async fn create_with_tx( | ||||
|         db: &mut Transaction<'_, Sqlite>, | ||||
|         text: &str, | ||||
|         relevant_for: &str, | ||||
|         keep_until: Option<NaiveDateTime>, | ||||
|     ) { | ||||
|         sqlx::query!( | ||||
|             "INSERT INTO activity(text, relevant_for, keep_until) VALUES (?, ?, ?)", | ||||
|             text, | ||||
|             relevant_for, | ||||
|             keep_until | ||||
|         ) | ||||
|         .execute(db.deref_mut()) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|     } | ||||
|  | ||||
|     pub async fn create( | ||||
|         db: &SqlitePool, | ||||
|         text: &str, | ||||
|         relevant_for: &str, | ||||
|         keep_until: Option<NaiveDateTime>, | ||||
|     ) { | ||||
|         let mut tx = db.begin().await.unwrap(); | ||||
|         Self::create_with_tx(&mut tx, text, relevant_for, keep_until).await; | ||||
|         tx.commit().await.unwrap(); | ||||
|     } | ||||
| } | ||||
| @@ -79,7 +79,9 @@ impl Mail { | ||||
|             .build(); | ||||
|  | ||||
|         // Send the email | ||||
|         mailer.send(&email).unwrap(); | ||||
|         if let Err(e) = mailer.send(&email) { | ||||
|             Log::create_with_tx(db, format!("Mail nicht versandt: {e:?}")).await; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ use self::{ | ||||
| use boatreservation::{BoatReservation, BoatReservationWithDetails}; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| pub mod activity; | ||||
| pub mod boat; | ||||
| pub mod boatdamage; | ||||
| pub mod boathouse; | ||||
|   | ||||
| @@ -1,12 +1,25 @@ | ||||
| // TODO: put back in `src/model/user/mod.rs` once that is cleaned up | ||||
|  | ||||
| use super::{AllowedToEditPaymentStatusUser, ManageUserUser, User}; | ||||
| use crate::model::{family::Family, log::Log, mail::valid_mails, role::Role}; | ||||
| use crate::model::{activity::Activity, family::Family, log::Log, mail::valid_mails, role::Role}; | ||||
| use chrono::NaiveDate; | ||||
| use rocket::{fs::TempFile, tokio::io::AsyncReadExt}; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| impl User { | ||||
|     pub(crate) async fn add_note( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         updated_by: &ManageUserUser, | ||||
|         note: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         let note = note.trim(); | ||||
|  | ||||
|         Activity::create(db, note, "relevant_for", None).await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn update_mail( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|   | ||||
							
								
								
									
										162
									
								
								src/model/user/clubmember.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/model/user/clubmember.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| use super::User; | ||||
| use crate::{ | ||||
|     model::{log::Log, notification::Notification, role::Role, user::ManageUserUser}, | ||||
|     special_user, | ||||
| }; | ||||
| use rocket::async_trait; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| special_user!(ClubMemberUser, +"Donau Linz", +"Förderndes Mitglied", +"Unterstützend"); | ||||
|  | ||||
| impl ClubMemberUser { | ||||
|     async fn add_membership_role(&self, db: &SqlitePool, role: &Role) { | ||||
|         sqlx::query!( | ||||
|             "INSERT INTO user_role(user_id, role_id) VALUES (?, ?)", | ||||
|             self.id, | ||||
|             role.id | ||||
|         ) | ||||
|         .execute(db) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|     } | ||||
|  | ||||
|     async fn remove_membership_role(&self, db: &SqlitePool) { | ||||
|         let role = Role::find_by_name(db, "Förderndes Mitglied").await.unwrap(); | ||||
|         sqlx::query!( | ||||
|             "DELETE FROM user_role WHERE user_id = ? and role_id = ?", | ||||
|             self.id, | ||||
|             role.id | ||||
|         ) | ||||
|         .execute(db) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let role = Role::find_by_name(db, "Unterstützend").await.unwrap(); | ||||
|         sqlx::query!( | ||||
|             "DELETE FROM user_role WHERE user_id = ? and role_id = ?", | ||||
|             self.id, | ||||
|             role.id | ||||
|         ) | ||||
|         .execute(db) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let role = Role::find_by_name(db, "Donau Linz").await.unwrap(); | ||||
|         sqlx::query!( | ||||
|             "DELETE FROM user_role WHERE user_id = ? and role_id = ?", | ||||
|             self.id, | ||||
|             role.id | ||||
|         ) | ||||
|         .execute(db) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|     } | ||||
|     async fn new_membership_role(&self, db: &SqlitePool, role: &str) -> Result<(), String> { | ||||
|         let role = Role::find_by_name(db, role).await.unwrap(); | ||||
|         self.remove_membership_role(db).await; | ||||
|         self.add_membership_role(db, &role).await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn move_to_regular( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         modified_by: &ManageUserUser, | ||||
|     ) -> Result<(), String> { | ||||
|         if self.has_role(db, "Donau Linz").await { | ||||
|             return Err(format!("User {self} ist bereits reguläres Mitglied.")); | ||||
|         } | ||||
|  | ||||
|         self.new_membership_role(db, "Donau Linz").await?; | ||||
|  | ||||
|         Notification::create_for_steering_people( | ||||
|             db, | ||||
|             &format!( | ||||
|                 "Liebe Steuerberechtigte, {} hat upgegraded und ist nun ein neues reguläres Mitglied. 🎉", | ||||
|                 self.name, | ||||
|             ), | ||||
|             "Neues Vereinsmitglied", | ||||
|             None, | ||||
|             None, | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         Log::create( | ||||
|             db, | ||||
|             format!("{modified_by} has moved user {self} to regular membership."), | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn move_to_unterstuetzend( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         modified_by: &ManageUserUser, | ||||
|     ) -> Result<(), String> { | ||||
|         if self.has_role(db, "Unterstützend").await { | ||||
|             return Err(format!("User {self} ist bereits unterstützendes Mitglied.")); | ||||
|         } | ||||
|  | ||||
|         self.new_membership_role(db, "Unterstützend").await?; | ||||
|  | ||||
|         if let Some(vorstand) = Role::find_by_name(db, "vorstand").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &vorstand, | ||||
|                 &format!( | ||||
|                     "Lieber Vorstand, der Mitgliedstatus von {} hat sich geändert auf 'Unterstützendes Mitglied'.", | ||||
|                     self.name, | ||||
|                 ), | ||||
|                 "Neues unterstützendes Vereinsmitglied", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|  | ||||
|         Log::create( | ||||
|             db, | ||||
|             format!("{modified_by} has moved user {self} to unterstützend membership."), | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn move_to_foerdernd( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         modified_by: &ManageUserUser, | ||||
|     ) -> Result<(), String> { | ||||
|         if self.has_role(db, "Förderndes Mitglied").await { | ||||
|             return Err(format!("User {self} ist bereits förderndes Mitglied.")); | ||||
|         } | ||||
|  | ||||
|         self.new_membership_role(db, "Förderndes Mitglied").await?; | ||||
|  | ||||
|         if let Some(vorstand) = Role::find_by_name(db, "vorstand").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &vorstand, | ||||
|                 &format!( | ||||
|                     "Lieber Vorstand, der Mitgliedstatus von {} hat sich geändert auf 'Förderndes Mitglied'.", | ||||
|                     self.name, | ||||
|                 ), | ||||
|                 "Neues förderndes Vereinsmitglied", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|  | ||||
|         Log::create( | ||||
|             db, | ||||
|             format!("{modified_by} has moved user {self} to fördernd membership."), | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								src/model/user/foerdernd.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/model/user/foerdernd.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| use super::User; | ||||
| use crate::{model::mail::Mail, special_user}; | ||||
| use rocket::async_trait; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| special_user!(FoerderndUser, +"Förderndes Mitglied"); | ||||
|  | ||||
| impl FoerderndUser { | ||||
|     pub(crate) async fn send_welcome_mail_to_user( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         let Some(mail) = &self.mail else { | ||||
|             return Err(format!( | ||||
|                 "Couldn't send welcome mail, as the user {self} has no mail..." | ||||
|             )); | ||||
|         }; | ||||
|  | ||||
|         Mail::send_single( | ||||
|             db, | ||||
|             mail, | ||||
|             "Willkommen im ASKÖ Ruderverein Donau Linz!", | ||||
|             format!( | ||||
| "Hallo {0}, | ||||
|  | ||||
| herzlich willkommen im ASKÖ Ruderverein Donau Linz! Wir freuen uns sehr, dich als neues Mitglied in unserem Verein begrüßen zu dürfen.  | ||||
|  | ||||
| Um dir den Einstieg zu erleichtern, findest du in unserem Handbuch alle wichtigen Informationen über unseren Verein: https://rudernlinz.at/book. Bei weiteren Fragen stehen dir die Adressen info@rudernlinz.at (für allgemeine Fragen) und it@rudernlinz.at (bei technischen Fragen) jederzeit zur Verfügung. | ||||
|  | ||||
| Damit du dich noch mehr verbunden fühlst (:-)), haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz' eingerichtet. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter. | ||||
|  | ||||
| Riemen- & Dollenbruch | ||||
| ASKÖ Ruderverein Donau Linz", self.name), | ||||
|             smtp_pw, | ||||
|         ).await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| @@ -8,14 +8,12 @@ use rocket::{ | ||||
|     http::{Cookie, Status}, | ||||
|     request::{FromRequest, Outcome}, | ||||
|     time::{Duration, OffsetDateTime}, | ||||
|     tokio::io::AsyncReadExt, | ||||
|     Request, | ||||
| }; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; | ||||
|  | ||||
| use super::{ | ||||
|     family::Family, | ||||
|     log::Log, | ||||
|     logbook::Logbook, | ||||
|     mail::Mail, | ||||
| @@ -26,14 +24,19 @@ use super::{ | ||||
|     tripdetails::TripDetails, | ||||
|     Day, | ||||
| }; | ||||
| use crate::{tera::admin::user::UserEditForm, AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD}; | ||||
| use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD; | ||||
| use scheckbuch::ScheckbuchUser; | ||||
|  | ||||
| mod basic; | ||||
| pub(crate) mod clubmember; | ||||
| mod fee; | ||||
| pub(crate) mod foerdernd; | ||||
| pub(crate) mod member; | ||||
| pub(crate) mod regular; | ||||
| pub(crate) mod scheckbuch; | ||||
| pub(crate) mod schnupperant; | ||||
| pub(crate) mod schnupperinterest; | ||||
| pub(crate) mod unterstuetzend; | ||||
|  | ||||
| #[derive(FromRow, Serialize, Deserialize, Clone, Debug, Eq, Hash, PartialEq)] | ||||
| pub struct User { | ||||
| @@ -120,7 +123,7 @@ impl User { | ||||
|         if self.has_role(db, "schnupperant").await { | ||||
|             self.send_welcome_mail_schnupper(db, mail, smtp_pw).await?; | ||||
|         } else if let Some(scheckbuch) = ScheckbuchUser::new(db, self).await { | ||||
|             scheckbuch.notify(db, mail, smtp_pw).await?; | ||||
|             scheckbuch.notify(db, smtp_pw).await?; | ||||
|         } else { | ||||
|             return Err(format!( | ||||
|                 "Could not send welcome mail, because user {} is not in Donau Linz or scheckbuch or schnupperant group", | ||||
| @@ -478,68 +481,6 @@ ORDER BY last_access DESC | ||||
|         .unwrap(); //Okay, because we can only create a User of a valid id | ||||
|     } | ||||
|  | ||||
|     pub async fn update(&self, db: &SqlitePool, data: UserEditForm<'_>) -> Result<(), String> { | ||||
|         let mut db = db.begin().await.map_err(|e| e.to_string())?; | ||||
|  | ||||
|         let mut family_id = data.family_id; | ||||
|  | ||||
|         if family_id.is_some_and(|x| x == -1) { | ||||
|             family_id = Some(Family::insert_tx(&mut db).await) | ||||
|         } | ||||
|  | ||||
|         if !self.has_membership_pdf_tx(&mut db).await { | ||||
|             if let Some(membership_pdf) = data.membership_pdf { | ||||
|                 let mut stream = membership_pdf.open().await.unwrap(); | ||||
|                 let mut buffer = Vec::new(); | ||||
|                 stream.read_to_end(&mut buffer).await.unwrap(); | ||||
|                 sqlx::query!( | ||||
|                     "UPDATE user SET membership_pdf = ? where id = ?", | ||||
|                     buffer, | ||||
|                     self.id | ||||
|                 ) | ||||
|                 .execute(db.deref_mut()) | ||||
|                 .await | ||||
|                 .unwrap(); //Okay, because we can only create a User of a valid id | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         sqlx::query!( | ||||
|             "UPDATE user SET dob = ?, weight = ?, sex = ?, member_since_date=?, birthdate=?, mail=?, nickname=?, notes=?, phone=?, address=?, family_id = ? where id = ?", | ||||
|             data.dob, | ||||
|             data.weight, | ||||
|             data.sex, | ||||
|             data.member_since_date, | ||||
|             data.birthdate, | ||||
|             data.mail, | ||||
|             data.nickname, | ||||
|             data.notes, | ||||
|             data.phone, | ||||
|             data.address, | ||||
|             family_id, | ||||
|             self.id | ||||
|         ) | ||||
|         .execute(db.deref_mut()) | ||||
|         .await | ||||
|         .unwrap(); //Okay, because we can only create a User of a valid id | ||||
|  | ||||
|         // handle roles | ||||
|         sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id) | ||||
|             .execute(db.deref_mut()) | ||||
|             .await | ||||
|             .unwrap(); | ||||
|  | ||||
|         for role_id in data.roles.into_keys() { | ||||
|             let role = Role::find_by_id_tx(&mut db, role_id.parse::<i32>().unwrap()) | ||||
|                 .await | ||||
|                 .unwrap(); | ||||
|             self.add_role_tx(&mut db, &role).await?; | ||||
|         } | ||||
|  | ||||
|         db.commit().await.map_err(|e| e.to_string())?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn send_end_mail_scheckbuch( | ||||
|         &self, | ||||
|         db: &mut Transaction<'_, Sqlite>, | ||||
| @@ -1010,9 +951,7 @@ impl UserWithMembershipPdf { | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use std::collections::HashMap; | ||||
|  | ||||
|     use crate::{tera::admin::user::UserEditForm, testdb}; | ||||
|     use crate::testdb; | ||||
|  | ||||
|     use super::User; | ||||
|     use sqlx::SqlitePool; | ||||
| @@ -1073,38 +1012,6 @@ mod test { | ||||
|         assert_eq!(User::create(&pool, "admin".into()).await, false); | ||||
|     } | ||||
|  | ||||
|     #[sqlx::test] | ||||
|     fn test_update() { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let user = User::find_by_id(&pool, 1).await.unwrap(); | ||||
|         user.update( | ||||
|             &pool, | ||||
|             UserEditForm { | ||||
|                 id: 1, | ||||
|                 dob: None, | ||||
|                 weight: None, | ||||
|                 sex: Some("m".into()), | ||||
|                 roles: HashMap::new(), | ||||
|                 member_since_date: None, | ||||
|                 birthdate: None, | ||||
|                 mail: None, | ||||
|                 nickname: None, | ||||
|                 notes: None, | ||||
|                 phone: None, | ||||
|                 address: None, | ||||
|                 family_id: None, | ||||
|                 membership_pdf: None, | ||||
|             }, | ||||
|         ) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         let user = User::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
|         assert_eq!(user.sex, Some("m".into())); | ||||
|     } | ||||
|  | ||||
|     #[sqlx::test] | ||||
|     fn succ_login_with_test_db() { | ||||
|         let pool = testdb!(); | ||||
|   | ||||
| @@ -1,22 +1,12 @@ | ||||
| use super::User; | ||||
| use crate::{ | ||||
|     model::{mail::Mail, notification::Notification}, | ||||
|     special_user, | ||||
| }; | ||||
| use crate::{model::mail::Mail, special_user}; | ||||
| use rocket::async_trait; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| special_user!(RegularUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); | ||||
| special_user!(RegularUser, +"Donau Linz"); | ||||
|  | ||||
| impl RegularUser { | ||||
|     pub(crate) async fn notify(&self, db: &SqlitePool, smtp_pw: &str) -> Result<(), String> { | ||||
|         self.notify_coxes_about_new_regular(db).await; | ||||
|         self.send_welcome_mail_to_user(db, smtp_pw).await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn send_welcome_mail_to_user( | ||||
|     pub(crate) async fn send_welcome_mail_to_user( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
| @@ -55,19 +45,4 @@ ASKÖ Ruderverein Donau Linz", self.name), | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn notify_coxes_about_new_regular(&self, db: &SqlitePool) { | ||||
|         Notification::create_for_steering_people( | ||||
|             db, | ||||
|             &format!( | ||||
|                 "Liebe Steuerberechtigte, seit {} gibt es ein neues Mitglied: {}", | ||||
|                 self.member_since_date.clone().unwrap(), | ||||
|                 self.name | ||||
|             ), | ||||
|             "Neues Vereinsmitglied", | ||||
|             None, | ||||
|             None, | ||||
|         ) | ||||
|         .await; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| use super::foerdernd::FoerderndUser; | ||||
| use super::regular::RegularUser; | ||||
| use super::unterstuetzend::UnterstuetzendUser; | ||||
| use super::{ManageUserUser, User}; | ||||
| use crate::model::role::Role; | ||||
| use crate::NonEmptyString; | ||||
| @@ -14,10 +16,9 @@ use sqlx::SqlitePool; | ||||
| special_user!(ScheckbuchUser, +"scheckbuch"); | ||||
|  | ||||
| impl ScheckbuchUser { | ||||
|     pub(crate) async fn convert_to_regular_user( | ||||
|         self, | ||||
|     async fn set_data_for_clubmember( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|         changed_by: &ManageUserUser, | ||||
|         member_since: &NaiveDate, | ||||
|         birthdate: &NaiveDate, | ||||
| @@ -25,7 +26,6 @@ impl ScheckbuchUser { | ||||
|         address: NonEmptyString, | ||||
|         membership_pdf: &TempFile<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         // Set data | ||||
|         self.user.update_birthdate(db, changed_by, birthdate).await; | ||||
|         self.user | ||||
|             .update_member_since(db, changed_by, member_since) | ||||
| @@ -37,6 +37,30 @@ impl ScheckbuchUser { | ||||
|             .add_membership_pdf(db, changed_by, membership_pdf) | ||||
|             .await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|     pub(crate) async fn convert_to_regular_user( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|         changed_by: &ManageUserUser, | ||||
|         member_since: &NaiveDate, | ||||
|         birthdate: &NaiveDate, | ||||
|         phone: NonEmptyString, | ||||
|         address: NonEmptyString, | ||||
|         membership_pdf: &TempFile<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         self.set_data_for_clubmember( | ||||
|             db, | ||||
|             changed_by, | ||||
|             member_since, | ||||
|             birthdate, | ||||
|             phone, | ||||
|             address, | ||||
|             membership_pdf, | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
|         // Change roles | ||||
|         let regular = Role::find_by_name(db, "Donau Linz").await.unwrap(); | ||||
|         let scheckbook = Role::find_by_name(db, "scheckbuch").await.unwrap(); | ||||
| @@ -45,33 +69,146 @@ impl ScheckbuchUser { | ||||
|  | ||||
|         // Notify | ||||
|         let regular = RegularUser::new(db, &self.user).await.unwrap(); | ||||
|         regular.notify(db, smtp_pw).await?; | ||||
|         regular.send_welcome_mail_to_user(db, smtp_pw).await?; | ||||
|         Notification::create_for_steering_people( | ||||
|             db, | ||||
|             &format!( | ||||
|                 "Liebe Steuerberechtigte, {} hatte ein Scheckbuch und ist nun seit {} es ein neues reguläres Mitglied. 🎉", | ||||
|                 self.name, | ||||
|                 self.member_since_date.clone().unwrap() | ||||
|             ), | ||||
|             "Neues Vereinsmitglied", | ||||
|             None, | ||||
|             None, | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn convert_to_unterstuetzend_user( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|         changed_by: &ManageUserUser, | ||||
|         member_since: &NaiveDate, | ||||
|         birthdate: &NaiveDate, | ||||
|         phone: NonEmptyString, | ||||
|         address: NonEmptyString, | ||||
|         membership_pdf: &TempFile<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         // Set data | ||||
|         self.set_data_for_clubmember( | ||||
|             db, | ||||
|             changed_by, | ||||
|             member_since, | ||||
|             birthdate, | ||||
|             phone, | ||||
|             address, | ||||
|             membership_pdf, | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
|         // Change roles | ||||
|         let unterstuetzend = Role::find_by_name(db, "Unterstützend").await.unwrap(); | ||||
|         let scheckbook = Role::find_by_name(db, "scheckbuch").await.unwrap(); | ||||
|         self.user.remove_role(db, changed_by, &scheckbook).await?; | ||||
|         self.user.add_role(db, changed_by, &unterstuetzend).await?; | ||||
|  | ||||
|         let unterstuetzend = UnterstuetzendUser::new(db, &self.user).await.unwrap(); | ||||
|         unterstuetzend | ||||
|             .send_welcome_mail_to_user(db, smtp_pw) | ||||
|             .await?; | ||||
|         if let Some(vorstand) = Role::find_by_name(db, "vorstand").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &vorstand, | ||||
|                 &format!( | ||||
|                     "Lieber Vorstand, {} hatte ein Scheckbuch und ist nun seit {} es ein neues unterstützendes Mitglied.", | ||||
|                     self.name, | ||||
|                     self.member_since_date.clone().unwrap() | ||||
|                 ), | ||||
|                 "Neues unterstützendes Vereinsmitglied", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn convert_to_foerdernd_user( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|         changed_by: &ManageUserUser, | ||||
|         member_since: &NaiveDate, | ||||
|         birthdate: &NaiveDate, | ||||
|         phone: NonEmptyString, | ||||
|         address: NonEmptyString, | ||||
|         membership_pdf: &TempFile<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         // Set data | ||||
|         self.set_data_for_clubmember( | ||||
|             db, | ||||
|             changed_by, | ||||
|             member_since, | ||||
|             birthdate, | ||||
|             phone, | ||||
|             address, | ||||
|             membership_pdf, | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
|         // Change roles | ||||
|         let unterstuetzend = Role::find_by_name(db, "Förderndes Mitglied").await.unwrap(); | ||||
|         let scheckbook = Role::find_by_name(db, "scheckbuch").await.unwrap(); | ||||
|         self.user.remove_role(db, changed_by, &scheckbook).await?; | ||||
|         self.user.add_role(db, changed_by, &unterstuetzend).await?; | ||||
|  | ||||
|         let foerdernd = FoerderndUser::new(db, &self.user).await.unwrap(); | ||||
|         foerdernd.send_welcome_mail_to_user(db, smtp_pw).await?; | ||||
|         if let Some(vorstand) = Role::find_by_name(db, "vorstand").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &vorstand, | ||||
|                 &format!( | ||||
|                     "Lieber Vorstand, {} hatte ein Scheckbuch und ist nun seit {} es ein neues förderndes Mitglied.", | ||||
|                     self.name, | ||||
|                     self.member_since_date.clone().unwrap() | ||||
|                 ), | ||||
|                 "Neues förderndes Vereinsmitglied", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     // TODO: make private | ||||
|     pub(crate) async fn notify( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         mail: &str, | ||||
|         smtp_pw: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         self.send_welcome_mail_to_user(db, mail, smtp_pw).await?; | ||||
|     pub(crate) async fn notify(&self, db: &SqlitePool, smtp_pw: &str) -> Result<(), String> { | ||||
|         self.notify_coxes_about_new_scheckbuch(db).await; | ||||
|         self.send_welcome_mail_to_user(db, smtp_pw).await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn send_welcome_mail_to_user( | ||||
|     pub(crate) async fn send_welcome_mail_to_user( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         mail: &str, | ||||
|         smtp_pw: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         let Some(mail) = &self.mail else { | ||||
|             return Err( | ||||
|                 "Kann Mail nicht versenden, weil der User keine Mailadresse hinterlegt hat.".into(), | ||||
|             ); | ||||
|         }; | ||||
|         Mail::send_single( | ||||
|             db, | ||||
|             mail, | ||||
|             &mail, | ||||
|             "ASKÖ Ruderverein Donau Linz | Dein Scheckbuch wartet auf Dich", | ||||
|             format!( | ||||
| "Hallo {0}, | ||||
|   | ||||
							
								
								
									
										319
									
								
								src/model/user/schnupperant.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								src/model/user/schnupperant.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,319 @@ | ||||
| use super::foerdernd::FoerderndUser; | ||||
| use super::regular::RegularUser; | ||||
| use super::scheckbuch::ScheckbuchUser; | ||||
| use super::unterstuetzend::UnterstuetzendUser; | ||||
| use super::{ManageUserUser, User}; | ||||
| use crate::model::role::Role; | ||||
| use crate::NonEmptyString; | ||||
| use crate::{ | ||||
|     model::{mail::Mail, notification::Notification}, | ||||
|     special_user, | ||||
| }; | ||||
| use chrono::NaiveDate; | ||||
| use rocket::async_trait; | ||||
| use rocket::fs::TempFile; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| special_user!(SchnupperantUser, +"schnupperant"); | ||||
|  | ||||
| impl SchnupperantUser { | ||||
|     async fn set_data_for_clubmember( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         changed_by: &ManageUserUser, | ||||
|         member_since: &NaiveDate, | ||||
|         birthdate: &NaiveDate, | ||||
|         phone: NonEmptyString, | ||||
|         address: NonEmptyString, | ||||
|         membership_pdf: &TempFile<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         self.user.update_birthdate(db, changed_by, birthdate).await; | ||||
|         self.user | ||||
|             .update_member_since(db, changed_by, member_since) | ||||
|             .await; | ||||
|  | ||||
|         self.user.update_phone(db, changed_by, &phone).await; | ||||
|         self.user.update_address(db, changed_by, &address).await; | ||||
|         self.user | ||||
|             .add_membership_pdf(db, changed_by, membership_pdf) | ||||
|             .await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|     pub(crate) async fn convert_to_regular_user( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|         changed_by: &ManageUserUser, | ||||
|         member_since: &NaiveDate, | ||||
|         birthdate: &NaiveDate, | ||||
|         phone: NonEmptyString, | ||||
|         address: NonEmptyString, | ||||
|         membership_pdf: &TempFile<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         self.set_data_for_clubmember( | ||||
|             db, | ||||
|             changed_by, | ||||
|             member_since, | ||||
|             birthdate, | ||||
|             phone, | ||||
|             address, | ||||
|             membership_pdf, | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
|         // Change roles | ||||
|         let regular = Role::find_by_name(db, "Donau Linz").await.unwrap(); | ||||
|         let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap(); | ||||
|         self.user.remove_role(db, changed_by, &scheckbook).await?; | ||||
|         self.user.add_role(db, changed_by, ®ular).await?; | ||||
|  | ||||
|         // Notify | ||||
|         let regular = RegularUser::new(db, &self.user).await.unwrap(); | ||||
|         regular.send_welcome_mail_to_user(db, smtp_pw).await?; | ||||
|         Notification::create_for_steering_people( | ||||
|             db, | ||||
|             &format!( | ||||
|                 "Liebe Steuerberechtigte, {} nahm an unserem Schnupperkurs teil und ist nun seit {} ein neues reguläres Mitglied. 🎉", | ||||
|                 self.name, | ||||
|                 self.member_since_date.clone().unwrap() | ||||
|             ), | ||||
|             "Neues Vereinsmitglied", | ||||
|             None, | ||||
|             None, | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn move_to_scheckbook( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         changed_by: &ManageUserUser, | ||||
|         smtp_pw: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         let schnupperant = Role::find_by_name(db, "schnupperant").await.unwrap(); | ||||
|         let scheckbook = Role::find_by_name(db, "scheckbuch").await.unwrap(); | ||||
|         self.user.remove_role(db, changed_by, &schnupperant).await?; | ||||
|         self.user.add_role(db, changed_by, &scheckbook).await?; | ||||
|  | ||||
|         if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await { | ||||
|             self.add_role(db, &changed_by, &no_einschreibgebuehr) | ||||
|                 .await | ||||
|                 .expect("role doesn't have a group"); | ||||
|         } | ||||
|  | ||||
|         let scheckbook = ScheckbuchUser::new(db, &self.user).await.unwrap(); | ||||
|         scheckbook.send_welcome_mail_to_user(db, smtp_pw).await?; | ||||
|  | ||||
|         Notification::create_for_steering_people( | ||||
|             db, | ||||
|             &format!( | ||||
|                 "Liebe Steuerberechtigte, {} hat unseren Schnupperkurs absolviert und nun ein Scheckbuch. Wie immer, freuen wir uns wenn du uns beim A+F Rudern unterstützt oder selber Ausfahrten ausschreibst. Bitte beachte, dass Scheckbuch-Personen nur Ausfahrten sehen, bei denen 'Scheckbuch-Anmeldungen erlauben' ausgewählt wurde.", | ||||
|                 self.name | ||||
|             ), | ||||
|             "Neues Scheckbuch", | ||||
|             None,None | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn move_to_schnupperinterest( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         changed_by: &ManageUserUser, | ||||
|     ) -> Result<(), String> { | ||||
|         let schnupperinterest = Role::find_by_name(db, "schnupper-interessierte") | ||||
|             .await | ||||
|             .unwrap(); | ||||
|         let schnupperant = Role::find_by_name(db, "schnupperant").await.unwrap(); | ||||
|         self.user.remove_role(db, changed_by, &schnupperant).await?; | ||||
|         self.user | ||||
|             .add_role(db, changed_by, &schnupperinterest) | ||||
|             .await?; | ||||
|  | ||||
|         if let Some(role) = Role::find_by_name(db, "schnupper-betreuer").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &role, | ||||
|                 &format!( | ||||
|                     "Lieber Schnupperbetreuer, {} hat sich vom Schnupperkurs abgemeldet.", | ||||
|                     self.name | ||||
|                 ), | ||||
|                 "Schnupperkurs Abmeldung", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn convert_to_unterstuetzend_user( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|         changed_by: &ManageUserUser, | ||||
|         member_since: &NaiveDate, | ||||
|         birthdate: &NaiveDate, | ||||
|         phone: NonEmptyString, | ||||
|         address: NonEmptyString, | ||||
|         membership_pdf: &TempFile<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         // Set data | ||||
|         self.set_data_for_clubmember( | ||||
|             db, | ||||
|             changed_by, | ||||
|             member_since, | ||||
|             birthdate, | ||||
|             phone, | ||||
|             address, | ||||
|             membership_pdf, | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
|         // Change roles | ||||
|         let unterstuetzend = Role::find_by_name(db, "Unterstützend").await.unwrap(); | ||||
|         let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap(); | ||||
|         self.user.remove_role(db, changed_by, &scheckbook).await?; | ||||
|         self.user.add_role(db, changed_by, &unterstuetzend).await?; | ||||
|         if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await { | ||||
|             self.add_role(db, &changed_by, &no_einschreibgebuehr) | ||||
|                 .await | ||||
|                 .expect("role doesn't have a group"); | ||||
|         } | ||||
|  | ||||
|         let unterstuetzend = UnterstuetzendUser::new(db, &self.user).await.unwrap(); | ||||
|         unterstuetzend | ||||
|             .send_welcome_mail_to_user(db, smtp_pw) | ||||
|             .await?; | ||||
|         if let Some(vorstand) = Role::find_by_name(db, "vorstand").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &vorstand, | ||||
|                 &format!( | ||||
|                     "Lieber Vorstand, {} nahm am Schnupperkurs teil und ist nun seit {} es ein neues unterstützendes Mitglied.", | ||||
|                     self.name, | ||||
|                     self.member_since_date.clone().unwrap() | ||||
|                 ), | ||||
|                 "Neues unterstützendes Vereinsmitglied", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn convert_to_foerdernd_user( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|         changed_by: &ManageUserUser, | ||||
|         member_since: &NaiveDate, | ||||
|         birthdate: &NaiveDate, | ||||
|         phone: NonEmptyString, | ||||
|         address: NonEmptyString, | ||||
|         membership_pdf: &TempFile<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         // Set data | ||||
|         self.set_data_for_clubmember( | ||||
|             db, | ||||
|             changed_by, | ||||
|             member_since, | ||||
|             birthdate, | ||||
|             phone, | ||||
|             address, | ||||
|             membership_pdf, | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
|         // Change roles | ||||
|         let unterstuetzend = Role::find_by_name(db, "Förderndes Mitglied").await.unwrap(); | ||||
|         let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap(); | ||||
|         self.user.remove_role(db, changed_by, &scheckbook).await?; | ||||
|         self.user.add_role(db, changed_by, &unterstuetzend).await?; | ||||
|         if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await { | ||||
|             self.add_role(db, &changed_by, &no_einschreibgebuehr) | ||||
|                 .await | ||||
|                 .expect("role doesn't have a group"); | ||||
|         } | ||||
|  | ||||
|         let foerdernd = FoerderndUser::new(db, &self.user).await.unwrap(); | ||||
|         foerdernd.send_welcome_mail_to_user(db, smtp_pw).await?; | ||||
|         if let Some(vorstand) = Role::find_by_name(db, "vorstand").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &vorstand, | ||||
|                 &format!( | ||||
|                     "Lieber Vorstand, {} nahm am Schnupperkurs teil und ist nun seit {} es ein neues förderndes Mitglied.", | ||||
|                     self.name, | ||||
|                     self.member_since_date.clone().unwrap() | ||||
|                 ), | ||||
|                 "Neues förderndes Vereinsmitglied", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     // TODO: make private | ||||
|     pub(crate) async fn notify(&self, db: &SqlitePool, smtp_pw: &str) -> Result<(), String> { | ||||
|         self.notify_coxes_about_new_scheckbuch(db).await; | ||||
|         self.send_welcome_mail_to_user(db, smtp_pw).await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn send_welcome_mail_to_user( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         let Some(mail) = &self.mail else { | ||||
|             return Err(format!( | ||||
|                 "Couldn't send mail, because user {self} has no mail" | ||||
|             )); | ||||
|         }; | ||||
|         Mail::send_single( | ||||
|             db, | ||||
|             &mail, | ||||
|             "ASKÖ Ruderverein Donau Linz | Anmeldung Schnupperkurs", | ||||
|             format!( | ||||
| "Hallo {0}, | ||||
|  | ||||
| es freut uns sehr, dich bei unserem Schnupperkurs willkommen heißen zu dürfen. Detaillierte Informationen folgen noch, ich werde sie dir ein paar Tage vor dem Termin zusenden. | ||||
|  | ||||
| Riemen- & Dollenbruch, | ||||
| ASKÖ Ruderverein Donau Linz", self.name), | ||||
|             smtp_pw, | ||||
|         ).await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn notify_coxes_about_new_scheckbuch(&self, db: &SqlitePool) { | ||||
|         if let Some(role) = Role::find_by_name(db, "schnupper-betreuer").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &role, | ||||
|                 &format!( | ||||
|                     "Lieber Schnupperbetreuer, {} hat sich zum Schnupperkurs angemeldet.", | ||||
|                     self.name | ||||
|                 ), | ||||
|                 "Neuer Schnupperant", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										103
									
								
								src/model/user/schnupperinterest.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/model/user/schnupperinterest.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| use super::scheckbuch::ScheckbuchUser; | ||||
| use super::schnupperant::SchnupperantUser; | ||||
| use super::{ManageUserUser, User}; | ||||
| use crate::model::role::Role; | ||||
| use crate::{model::notification::Notification, special_user}; | ||||
| use rocket::async_trait; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| special_user!(SchnupperInterestUser, +"schnupper-interessierte"); | ||||
|  | ||||
| impl SchnupperInterestUser { | ||||
|     pub(crate) async fn move_to_scheckbook( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         changed_by: &ManageUserUser, | ||||
|         smtp_pw: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         let schnupperinterest = Role::find_by_name(db, "schnupper-interessierte") | ||||
|             .await | ||||
|             .unwrap(); | ||||
|         let scheckbook = Role::find_by_name(db, "scheckbuch").await.unwrap(); | ||||
|         self.user | ||||
|             .remove_role(db, changed_by, &schnupperinterest) | ||||
|             .await?; | ||||
|         self.user.add_role(db, changed_by, &scheckbook).await?; | ||||
|  | ||||
|         let scheckbook = ScheckbuchUser::new(db, &self.user).await.unwrap(); | ||||
|         scheckbook.send_welcome_mail_to_user(db, smtp_pw).await?; | ||||
|  | ||||
|         Notification::create_for_steering_people( | ||||
|             db, | ||||
|             &format!( | ||||
|                 "Liebe Steuerberechtigte, {} wollte unseren Schnupperkurs absolviert und nun ein Scheckbuch. Wie immer, freuen wir uns wenn du uns beim A+F Rudern unterstützt oder selber Ausfahrten ausschreibst. Bitte beachte, dass Scheckbuch-Personen nur Ausfahrten sehen, bei denen 'Scheckbuch-Anmeldungen erlauben' ausgewählt wurde.", | ||||
|                 self.name | ||||
|             ), | ||||
|             "Neues Scheckbuch", | ||||
|             None, | ||||
|             None | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn move_to_schnupperant( | ||||
|         self, | ||||
|         db: &SqlitePool, | ||||
|         changed_by: &ManageUserUser, | ||||
|         smtp_pw: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         let schnupperinterest = Role::find_by_name(db, "schnupper-interessierte") | ||||
|             .await | ||||
|             .unwrap(); | ||||
|         let schnupperant = Role::find_by_name(db, "schnupperant").await.unwrap(); | ||||
|         self.user | ||||
|             .remove_role(db, changed_by, &schnupperinterest) | ||||
|             .await?; | ||||
|         self.user.add_role(db, changed_by, &schnupperant).await?; | ||||
|  | ||||
|         let schnupperant = SchnupperantUser::new(db, &self.user).await.unwrap(); | ||||
|         schnupperant.notify(db, smtp_pw).await?; | ||||
|  | ||||
|         if let Some(role) = Role::find_by_name(db, "schnupper-betreuer").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &role, | ||||
|                 &format!( | ||||
|                     "Lieber Schnupperbetreuer, {} hat sich zum Schnupperkurs angemeldet.", | ||||
|                     self.name | ||||
|                 ), | ||||
|                 "Neuer Schnupper-Interessierte:r", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn notify(&self, db: &SqlitePool) -> Result<(), String> { | ||||
|         self.notify_schnupperbetreuer_about_new_interest(db).await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn notify_schnupperbetreuer_about_new_interest(&self, db: &SqlitePool) { | ||||
|         if let Some(role) = Role::find_by_name(db, "schnupper-betreuer").await { | ||||
|             Notification::create_for_role( | ||||
|                 db, | ||||
|                 &role, | ||||
|                 &format!( | ||||
|                     "Lieber Schnupperbetreuer, {} hat Interesse zum Schnupperkurs bekundet.", | ||||
|                     self.name | ||||
|                 ), | ||||
|                 "Neuer Schnupper-Interessierte:r", | ||||
|                 None, | ||||
|                 None, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								src/model/user/unterstuetzend.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/model/user/unterstuetzend.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| use super::User; | ||||
| use crate::{model::mail::Mail, special_user}; | ||||
| use rocket::async_trait; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| special_user!(UnterstuetzendUser, +"Unterstützend"); | ||||
|  | ||||
| impl UnterstuetzendUser { | ||||
|     pub(crate) async fn send_welcome_mail_to_user( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         smtp_pw: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         let Some(mail) = &self.mail else { | ||||
|             return Err(format!( | ||||
|                 "Couldn't send welcome mail, as the user {self} has no mail..." | ||||
|             )); | ||||
|         }; | ||||
|  | ||||
|         Mail::send_single( | ||||
|             db, | ||||
|             mail, | ||||
|             "Willkommen im ASKÖ Ruderverein Donau Linz!", | ||||
|             format!( | ||||
| "Hallo {0}, | ||||
|  | ||||
| herzlich willkommen im ASKÖ Ruderverein Donau Linz! Wir freuen uns sehr, dich als neues Mitglied in unserem Verein begrüßen zu dürfen.  | ||||
|  | ||||
| Um dir den Einstieg zu erleichtern, findest du in unserem Handbuch alle wichtigen Informationen über unseren Verein: https://rudernlinz.at/book. Bei weiteren Fragen stehen dir die Adressen info@rudernlinz.at (für allgemeine Fragen) und it@rudernlinz.at (bei technischen Fragen) jederzeit zur Verfügung. | ||||
|  | ||||
| Damit du dich noch mehr verbunden fühlst (:-)), haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz' eingerichtet. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter. | ||||
|  | ||||
| Riemen- & Dollenbruch | ||||
| ASKÖ Ruderverein Donau Linz", self.name), | ||||
|             smtp_pw, | ||||
|         ).await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +1,3 @@ | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use crate::{ | ||||
|     model::{ | ||||
|         family::Family, | ||||
| @@ -7,9 +5,10 @@ use crate::{ | ||||
|         logbook::Logbook, | ||||
|         role::Role, | ||||
|         user::{ | ||||
|             member::Member, scheckbuch::ScheckbuchUser, AdminUser, AllowedToEditPaymentStatusUser, | ||||
|             ManageUserUser, User, UserWithDetails, UserWithMembershipPdf, | ||||
|             UserWithRolesAndMembershipPdf, VorstandUser, | ||||
|             clubmember::ClubMemberUser, member::Member, scheckbuch::ScheckbuchUser, | ||||
|             schnupperant::SchnupperantUser, schnupperinterest::SchnupperInterestUser, AdminUser, | ||||
|             AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails, | ||||
|             UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, | ||||
|         }, | ||||
|     }, | ||||
|     tera::Config, | ||||
| @@ -300,49 +299,6 @@ async fn delete(db: &State<SqlitePool>, admin: ManageUserUser, user: i32) -> Fla | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm, Debug)] | ||||
| pub struct UserEditForm<'a> { | ||||
|     pub(crate) id: i32, | ||||
|     pub(crate) dob: Option<String>, | ||||
|     pub(crate) weight: Option<String>, | ||||
|     pub(crate) sex: Option<String>, | ||||
|     pub(crate) roles: HashMap<String, String>, | ||||
|     pub(crate) member_since_date: Option<String>, | ||||
|     pub(crate) birthdate: Option<String>, | ||||
|     pub(crate) mail: Option<String>, | ||||
|     pub(crate) nickname: Option<String>, | ||||
|     pub(crate) notes: Option<String>, | ||||
|     pub(crate) phone: Option<String>, | ||||
|     pub(crate) address: Option<String>, | ||||
|     pub(crate) family_id: Option<i64>, | ||||
|     pub(crate) membership_pdf: Option<TempFile<'a>>, | ||||
| } | ||||
|  | ||||
| #[post("/user", data = "<data>", format = "multipart/form-data")] | ||||
| async fn update( | ||||
|     db: &State<SqlitePool>, | ||||
|     data: Form<UserEditForm<'_>>, | ||||
|     admin: ManageUserUser, | ||||
| ) -> Flash<Redirect> { | ||||
|     let user = User::find_by_id(db, data.id).await; | ||||
|     Log::create( | ||||
|         db, | ||||
|         format!("{} updated user from {user:?} to {data:?}", admin.user.name), | ||||
|     ) | ||||
|     .await; | ||||
|     let Some(user) = user else { | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User with ID {} does not exist!", data.id), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     match user.update(db, data.into_inner()).await { | ||||
|         Ok(_) => Flash::success(Redirect::to("/admin/user"), "Successfully updated user"), | ||||
|         Err(e) => Flash::error(Redirect::to("/admin/user"), e), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm, Debug)] | ||||
| pub struct MailUpdateForm { | ||||
|     mail: String, | ||||
| @@ -371,6 +327,34 @@ async fn update_mail( | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm, Debug)] | ||||
| pub struct AddNoteForm { | ||||
|     note: String, | ||||
| } | ||||
|  | ||||
| #[post("/user/<id>/add-note", data = "<data>")] | ||||
| async fn add_note( | ||||
|     db: &State<SqlitePool>, | ||||
|     data: Form<AddNoteForm>, | ||||
|     admin: ManageUserUser, | ||||
|     id: i32, | ||||
| ) -> Flash<Redirect> { | ||||
|     let Some(user) = User::find_by_id(db, id).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User with ID {} does not exist!", id), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     match user.add_note(db, &admin, &data.note).await { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to(format!("/admin/user/{}", user.id)), | ||||
|             "Notiz hinzugefügt", | ||||
|         ), | ||||
|         Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", user.id)), e), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm, Debug)] | ||||
| pub struct PhoneUpdateForm { | ||||
|     phone: String, | ||||
| @@ -711,11 +695,11 @@ async fn create( | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm, Debug)] | ||||
| struct UserAddScheckbuchForm<'r> { | ||||
|     name: &'r str, | ||||
|     mail: &'r str, | ||||
| } | ||||
| //#[derive(FromForm, Debug)] | ||||
| //struct UserAddScheckbuchForm<'r> { | ||||
| //    name: &'r str, | ||||
| //    mail: &'r str, | ||||
| //} | ||||
|  | ||||
| //#[post("/user/new/scheckbuch", data = "<data>")] | ||||
| //async fn create_scheckbuch( | ||||
| @@ -764,58 +748,128 @@ struct UserAddScheckbuchForm<'r> { | ||||
| //    Flash::success(Redirect::to("/admin/user/scheckbuch"), format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {mail} verschickt.")) | ||||
| //} | ||||
|  | ||||
| //#[get("/user/move/schnupperant/<id>/to/scheckbuch")] | ||||
| //async fn schnupper_to_scheckbuch( | ||||
| //    db: &State<SqlitePool>, | ||||
| //    id: i32, | ||||
| //    admin: SchnupperBetreuerUser, | ||||
| //    config: &State<Config>, | ||||
| //) -> Flash<Redirect> { | ||||
| //    let Some(user) = User::find_by_id(db, id).await else { | ||||
| //        return Flash::error( | ||||
| //            Redirect::to("/admin/schnupper"), | ||||
| //            "user id not found".to_string(), | ||||
| //        ); | ||||
| //    }; | ||||
| // | ||||
| //    if !user.has_role(db, "schnupperant").await { | ||||
| //        return Flash::error( | ||||
| //            Redirect::to("/admin/schnupper"), | ||||
| //            "kein schnupperant...".to_string(), | ||||
| //        ); | ||||
| //    } | ||||
| // | ||||
| //    let schnupperant = Role::find_by_name(db, "schnupperant").await.unwrap(); | ||||
| //    let paid = Role::find_by_name(db, "paid").await.unwrap(); | ||||
| //    user.remove_role(db, &schnupperant).await; | ||||
| //    user.remove_role(db, &paid).await; | ||||
| // | ||||
| //    let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap(); | ||||
| //    user.add_role(db, &scheckbuch) | ||||
| //        .await | ||||
| //        .expect("just removed 'schnupperant' thus can't have a role with that group"); | ||||
| // | ||||
| //    if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await { | ||||
| //        user.add_role(db, &no_einschreibgebuehr) | ||||
| //            .await | ||||
| //            .expect("role doesn't have a group"); | ||||
| //    } | ||||
| // | ||||
| //    user.send_welcome_email(db, &config.smtp_pw).await.unwrap(); | ||||
| // | ||||
| //    Log::create( | ||||
| //        db, | ||||
| //        format!( | ||||
| //            "{} created new scheckbuch (from schnupperant): {}", | ||||
| //            admin.name, user.name | ||||
| //        ), | ||||
| //    ) | ||||
| //    .await; | ||||
| //    Flash::success(Redirect::to("/admin/schnupper"), format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {} verschickt.", user.mail.unwrap())) | ||||
| //} | ||||
| #[derive(FromForm, Debug)] | ||||
| pub struct SchnupperantToRegularForm<'a> { | ||||
|     membertype: String, | ||||
|     member_since: String, | ||||
|     birthdate: String, | ||||
|     phone: String, | ||||
|     address: String, | ||||
|     membership_pdf: TempFile<'a>, | ||||
| } | ||||
|  | ||||
| #[post("/user/<id>/schnupperant-to-regular", data = "<data>")] | ||||
| async fn schnupperant_to_regular( | ||||
|     db: &State<SqlitePool>, | ||||
|     data: Form<SchnupperantToRegularForm<'_>>, | ||||
|     admin: ManageUserUser, | ||||
|     config: &State<Config>, | ||||
|     id: i32, | ||||
| ) -> Flash<Redirect> { | ||||
|     let Some(user) = User::find_by_id(db, id).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User with ID {} does not exist!", id), | ||||
|         ); | ||||
|     }; | ||||
|     let Ok(birthdate) = NaiveDate::parse_from_str(&data.birthdate, "%Y-%m-%d") else { | ||||
|         return Flash::error( | ||||
|             Redirect::to(format!("/admin/user/{id}")), | ||||
|             format!( | ||||
|                 "Geburtsdatum {} ist nicht im YYYY-MM-DD Format", | ||||
|                 &data.birthdate | ||||
|             ), | ||||
|         ); | ||||
|     }; | ||||
|     let Ok(member_since) = NaiveDate::parse_from_str(&data.member_since, "%Y-%m-%d") else { | ||||
|         return Flash::error( | ||||
|             Redirect::to(format!("/admin/user/{id}")), | ||||
|             format!( | ||||
|                 "Beitrittsdatum {} ist nicht im YYYY-MM-DD Format", | ||||
|                 &data.birthdate | ||||
|             ), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     let Some(user) = SchnupperantUser::new(db, &user).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to(format!("/admin/user/{id}")), | ||||
|             "User ist kein Schnupperant", | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     let Ok(phone) = data.phone.clone().try_into() else { | ||||
|         return Flash::error( | ||||
|             Redirect::to(format!("/admin/user/{id}")), | ||||
|             "Vereinsmitglied braucht eine Telefonnummer", | ||||
|         ); | ||||
|     }; | ||||
|     let Ok(address) = data.address.clone().try_into() else { | ||||
|         return Flash::error( | ||||
|             Redirect::to(format!("/admin/user/{id}")), | ||||
|             "Vereinsmitglied braucht eine Adresse", | ||||
|         ); | ||||
|     }; | ||||
|     let response = match &*data.membertype { | ||||
|         "regular" => { | ||||
|             user.convert_to_regular_user( | ||||
|                 db, | ||||
|                 &config.smtp_pw, | ||||
|                 &admin, | ||||
|                 &member_since, | ||||
|                 &birthdate, | ||||
|                 phone, | ||||
|                 address, | ||||
|                 &data.membership_pdf, | ||||
|             ) | ||||
|             .await | ||||
|         } | ||||
|         "unterstuetzend" => { | ||||
|             user.convert_to_unterstuetzend_user( | ||||
|                 db, | ||||
|                 &config.smtp_pw, | ||||
|                 &admin, | ||||
|                 &member_since, | ||||
|                 &birthdate, | ||||
|                 phone, | ||||
|                 address, | ||||
|                 &data.membership_pdf, | ||||
|             ) | ||||
|             .await | ||||
|         } | ||||
|         "foerdernd" => { | ||||
|             user.convert_to_foerdernd_user( | ||||
|                 db, | ||||
|                 &config.smtp_pw, | ||||
|                 &admin, | ||||
|                 &member_since, | ||||
|                 &birthdate, | ||||
|                 phone, | ||||
|                 address, | ||||
|                 &data.membership_pdf, | ||||
|             ) | ||||
|             .await | ||||
|         } | ||||
|         _ => { | ||||
|             return Flash::error( | ||||
|                 Redirect::to(format!("/admin/user/{id}")), | ||||
|                 "Membertype gibts ned", | ||||
|             ) | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     match response { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to(format!("/admin/user/{}", id)), | ||||
|             "Mitgliedstyp umgewandelt und Infos versendet", | ||||
|         ), | ||||
|         Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", id)), e), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm, Debug)] | ||||
| pub struct ScheckToRegularForm<'a> { | ||||
|     membertype: String, | ||||
|     member_since: String, | ||||
|     birthdate: String, | ||||
|     phone: String, | ||||
| @@ -875,20 +929,220 @@ async fn scheckbook_to_regular( | ||||
|             "Vereinsmitglied braucht eine Adresse", | ||||
|         ); | ||||
|     }; | ||||
|     let response = match &*data.membertype { | ||||
|         "regular" => { | ||||
|             user.convert_to_regular_user( | ||||
|                 db, | ||||
|                 &config.smtp_pw, | ||||
|                 &admin, | ||||
|                 &member_since, | ||||
|                 &birthdate, | ||||
|                 phone, | ||||
|                 address, | ||||
|                 &data.membership_pdf, | ||||
|             ) | ||||
|             .await | ||||
|         } | ||||
|         "unterstuetzend" => { | ||||
|             user.convert_to_unterstuetzend_user( | ||||
|                 db, | ||||
|                 &config.smtp_pw, | ||||
|                 &admin, | ||||
|                 &member_since, | ||||
|                 &birthdate, | ||||
|                 phone, | ||||
|                 address, | ||||
|                 &data.membership_pdf, | ||||
|             ) | ||||
|             .await | ||||
|         } | ||||
|         "foerdernd" => { | ||||
|             user.convert_to_foerdernd_user( | ||||
|                 db, | ||||
|                 &config.smtp_pw, | ||||
|                 &admin, | ||||
|                 &member_since, | ||||
|                 &birthdate, | ||||
|                 phone, | ||||
|                 address, | ||||
|                 &data.membership_pdf, | ||||
|             ) | ||||
|             .await | ||||
|         } | ||||
|         _ => { | ||||
|             return Flash::error( | ||||
|                 Redirect::to(format!("/admin/user/{id}")), | ||||
|                 "Membertype gibts ned", | ||||
|             ) | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     match user | ||||
|         .convert_to_regular_user( | ||||
|             db, | ||||
|             &config.smtp_pw, | ||||
|             &admin, | ||||
|             &member_since, | ||||
|             &birthdate, | ||||
|             phone, | ||||
|             address, | ||||
|             &data.membership_pdf, | ||||
|         ) | ||||
|         .await | ||||
|     { | ||||
|     match response { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to(format!("/admin/user/{}", id)), | ||||
|             "Mitgliedstyp umgewandelt und Infos versendet", | ||||
|         ), | ||||
|         Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", id)), e), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm, Debug)] | ||||
| pub struct ChangeMembertypeForm { | ||||
|     membertype: String, | ||||
| } | ||||
|  | ||||
| #[post("/user/<id>/change-membertype", data = "<data>")] | ||||
| async fn change_membertype( | ||||
|     db: &State<SqlitePool>, | ||||
|     admin: ManageUserUser, | ||||
|     data: Form<ChangeMembertypeForm>, | ||||
|     id: i32, | ||||
| ) -> Flash<Redirect> { | ||||
|     let Some(user) = User::find_by_id(db, id).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User with ID {} does not exist!", id), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     let Some(user) = ClubMemberUser::new(&db, &user).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User {user} ist kein Vereinsmitglied"), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     let response = match &*data.membertype { | ||||
|         "regular" => user.move_to_regular(db, &admin).await, | ||||
|         "unterstuetzend" => user.move_to_unterstuetzend(db, &admin).await, | ||||
|         "foerdernd" => user.move_to_foerdernd(db, &admin).await, | ||||
|         _ => { | ||||
|             return Flash::error( | ||||
|                 Redirect::to(format!("/admin/user/{{ id }}")), | ||||
|                 format!("Membertype gibt's ned"), | ||||
|             ) | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     match response { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to(format!("/admin/user/{}", id)), | ||||
|             "Mitgliedstyp umgewandelt und Infos versendet", | ||||
|         ), | ||||
|         Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", id)), e), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[get("/user/<id>/schnupperant-to-scheckbuch")] | ||||
| async fn schnupperant_to_scheckbook( | ||||
|     db: &State<SqlitePool>, | ||||
|     admin: ManageUserUser, | ||||
|     config: &State<Config>, | ||||
|     id: i32, | ||||
| ) -> Flash<Redirect> { | ||||
|     let Some(user) = User::find_by_id(db, id).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User with ID {} does not exist!", id), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     let Some(user) = SchnupperantUser::new(&db, &user).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to(format!("/admin/user/{id}")), | ||||
|             format!("User {user} ist kein Schnupperant"), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     match user.move_to_scheckbook(db, &admin, &config.smtp_pw).await { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to(format!("/admin/user/{}", id)), | ||||
|             "Mitgliedstyp umgewandelt und Infos versendet", | ||||
|         ), | ||||
|         Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", id)), e), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[get("/user/<id>/schnupperinterest-to-schnupperant")] | ||||
| async fn schnupperinterest_to_schnupperant( | ||||
|     db: &State<SqlitePool>, | ||||
|     admin: ManageUserUser, | ||||
|     config: &State<Config>, | ||||
|     id: i32, | ||||
| ) -> Flash<Redirect> { | ||||
|     let Some(user) = User::find_by_id(db, id).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User with ID {} does not exist!", id), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     let Some(user) = SchnupperInterestUser::new(&db, &user).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to(format!("/admin/user/{id}")), | ||||
|             format!("User {user} ist kein Schnupperinteressierter"), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     match user.move_to_schnupperant(db, &admin, &config.smtp_pw).await { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to(format!("/admin/user/{}", id)), | ||||
|             "Mitgliedstyp umgewandelt und Infos versendet", | ||||
|         ), | ||||
|         Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", id)), e), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[get("/user/<id>/schnupperant-to-schnupperinterest")] | ||||
| async fn schnupperant_to_schnupperinterest( | ||||
|     db: &State<SqlitePool>, | ||||
|     admin: ManageUserUser, | ||||
|     id: i32, | ||||
| ) -> Flash<Redirect> { | ||||
|     let Some(user) = User::find_by_id(db, id).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User with ID {} does not exist!", id), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     let Some(user) = SchnupperantUser::new(&db, &user).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to(format!("/admin/user/{id}")), | ||||
|             format!("User {user} ist kein Schnupperant"), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     match user.move_to_schnupperinterest(db, &admin).await { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to(format!("/admin/user/{}", id)), | ||||
|             "Mitgliedstyp umgewandelt.", | ||||
|         ), | ||||
|         Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", id)), e), | ||||
|     } | ||||
| } | ||||
| #[get("/user/<id>/schnupperinterest-to-scheckbuch")] | ||||
| async fn schnupperinterest_to_scheckbuch( | ||||
|     db: &State<SqlitePool>, | ||||
|     admin: ManageUserUser, | ||||
|     config: &State<Config>, | ||||
|     id: i32, | ||||
| ) -> Flash<Redirect> { | ||||
|     let Some(user) = User::find_by_id(db, id).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User with ID {} does not exist!", id), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     let Some(user) = SchnupperInterestUser::new(&db, &user).await else { | ||||
|         return Flash::error( | ||||
|             Redirect::to(format!("/admin/user/{id}")), | ||||
|             format!("User {user} ist kein Schnupperinteressierter"), | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     match user.move_to_scheckbook(db, &admin, &config.smtp_pw).await { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to(format!("/admin/user/{}", id)), | ||||
|             "Mitgliedstyp umgewandelt und Infos versendet", | ||||
| @@ -903,10 +1157,8 @@ pub fn routes() -> Vec<Route> { | ||||
|         index_admin, | ||||
|         view, | ||||
|         resetpw, | ||||
|         update, | ||||
|         create, | ||||
|         //create_scheckbuch, | ||||
|         //schnupper_to_scheckbuch, | ||||
|         delete, | ||||
|         fees, | ||||
|         fees_paid, | ||||
| @@ -923,8 +1175,15 @@ pub fn routes() -> Vec<Route> { | ||||
|         update_family, | ||||
|         add_membership_pdf, | ||||
|         add_role, | ||||
|         add_note, | ||||
|         remove_role, | ||||
|         // | ||||
|         scheckbook_to_regular, | ||||
|         schnupperant_to_regular, | ||||
|         schnupperant_to_scheckbook, | ||||
|         schnupperinterest_to_schnupperant, | ||||
|         schnupperant_to_schnupperinterest, | ||||
|         schnupperinterest_to_scheckbuch, | ||||
|         change_membertype, | ||||
|     ] | ||||
| } | ||||
|   | ||||
| @@ -25,3 +25,11 @@ UPDATE role SET desc='Es können Logbucheinträge im Nachhinein hinzugefügt wer | ||||
| UPDATE role SET desc='Erlaubt den Login auf der Wordpress-Website um zB Artikel zu schreiben.' WHERE name='allow_website_login'; | ||||
| UPDATE role SET desc='Muss nur den halben Rennruderbeitrag bezahlen (da zB erst in der 2. Jahreshälfte dazugestoßen wurde)' WHERE name='half-rennrudern'; | ||||
| UPDATE role SET desc='Muss keinen Rennruderbeitrag bezahlen, obwohl man in Rennruder-Gruppe ist.' WHERE name='renntrainer'; | ||||
|  | ||||
| CREATE TABLE activity ( | ||||
|     id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||
|     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||
|     text TEXT NOT NULL, | ||||
|     relevant_for TEXT NOT NULL,  -- e.g. user_id=123;trip_id=456 | ||||
|     keep_until DATETIME          -- OPTIONAL field | ||||
| ); | ||||
|   | ||||
| @@ -67,115 +67,21 @@ | ||||
|         {% for user in users %} | ||||
|             <div data-filterable="true" | ||||
|                  data-filter="{{ user.name }} {% for role in roles %} {% if role.name in user.roles %} yes-role:{{ role.name }} {% else %} no-role:{{ role.name }} {% endif %} role-{{ role }} {% endfor %} {% if user.membership_pdf %}has-membership-pdf{% else %}has-no-membership-pdf{% endif %}" | ||||
|                  class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative"> | ||||
|                 <details class="block dark:text-white w-full"> | ||||
|                     <summary> | ||||
|                         <span class="text-black dark:text-white cursor-pointer"> | ||||
|                             <span class="font-bold"> | ||||
|                                 {{ user.name }} | ||||
|                                 {% if not user.last_access and allowed_to_edit and user.mail %} | ||||
|                                     <form action="/admin/user" | ||||
|                                           method="post" | ||||
|                                           enctype="multipart/form-data" | ||||
|                                           class="inline"> | ||||
|                                         • <a class="font-normal text-primary-600 dark:text-primary-200 hover:text-primary-900 dark:hover:text-primary-300 underline" | ||||
|     href="/admin/user/{{ user.id }}/send-welcome-mail" | ||||
|     onclick="return confirm('Willst du wirklich das Willkommensmail an {{ user.name }} ausschicken?');">Willkommensmail verschicken</a> | ||||
|                                     </form> | ||||
|                                 {% endif %} | ||||
|                                 {% if user.last_access %}• ⏳ {{ user.last_access | date }}{% endif %} | ||||
|                             </span> | ||||
|                             <small class="block text-gray-600 dark:text-gray-100"> | ||||
|                                 {% for role in user.roles -%} | ||||
|                                     {{ role }} | ||||
|                                     {%- if not loop.last %}, | ||||
|                                     {% endif -%} | ||||
|                                 {% endfor %} | ||||
|                             </small> | ||||
|                         </span> | ||||
|                     </summary> | ||||
|                     <a class="block my-1 font-normal text-[#f43f5e] dark:text-primary-200 hover:text-primary-900 dark:hover:text-primary-300 underline" | ||||
|                        href="/admin/user/{{ user.id }}">✏️</a> | ||||
|                     <form action="/admin/user" | ||||
|                           method="post" | ||||
|                           enctype="multipart/form-data" | ||||
|                           class="w-full mt-2"> | ||||
|                         {% if user.pw %} | ||||
|                             <a class="block my-1 font-normal text-[#f43f5e] dark:text-primary-200 hover:text-primary-900 dark:hover:text-primary-300 underline" | ||||
|                                href="/admin/user/{{ user.id }}/reset-pw" | ||||
|                                onclick="return confirm('Willst du wirklich das Passwort zurücksetzen?');">Passwort zurücksetzen</a> | ||||
|                         {% endif %} | ||||
|                         <div class="w-full grid gap-3 mt-3"> | ||||
|                             <input type="hidden" name="id" value="{{ user.id }}" /> | ||||
|                             <div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-3"> | ||||
|                                 {% for cluster, cluster_roles in roles | group_by(attribute="cluster") %} | ||||
|                                     <label for="cluster_{{ loop.index }}">{{ cluster }}</label> | ||||
|                                     {# Determine the initially selected role within the cluster #} | ||||
|                                     {% set_global selected_role_id = "none" %} | ||||
|                                     {% for role in cluster_roles %} | ||||
|                                         {% if selected_role_id == "none" and role.name in user.roles %} | ||||
|                                             {% set_global selected_role_id = role.id %} | ||||
|                                         {% endif %} | ||||
|                                     {% endfor %} | ||||
|                                     {# Set default name to the selected role ID or first role if none selected #} | ||||
|                                     <select id="cluster_{{ loop.index }}" | ||||
|                                             {% if selected_role_id == 'none' %} {% else %} name="roles[{{ selected_role_id }}]" {% endif %} | ||||
|                                             {% if allowed_to_edit == false %}disabled{% endif %} | ||||
|                                             onchange=" if (this.value === '') { this.removeAttribute('name'); } else { this.name = 'roles[' + this.options[this.selectedIndex].getAttribute('data-role-id') + ']'; }"> | ||||
|                                         <option value="" | ||||
|                                                 data-role-id="none" | ||||
|                                                 {% if selected_role_id == 'none' %}selected="selected"{% endif %}> | ||||
|                                             None | ||||
|                                         </option> | ||||
|                                         {% for role in cluster_roles %} | ||||
|                                             <option value="on" | ||||
|                                                     data-role-id="{{ role.id }}" | ||||
|                                                     {% if role.id == selected_role_id %}selected="selected"{% endif %}> | ||||
|                                                 {{ role.name }} | ||||
|                                             </option> | ||||
|                                         {% endfor %} | ||||
|                                     </select> | ||||
|                                 {% endfor %} | ||||
|                                 {% for role in roles %} | ||||
|                                     {% if not role.cluster %} | ||||
|                                         {{ macros::checkbox(label=role.name, name="roles[" ~ role.id ~ "]", id=loop.index , checked=role.name in user.roles, disabled=allowed_to_edit == false) }} | ||||
|                                     {% endif %} | ||||
|                                 {% endfor %} | ||||
|                                 <hr class="sm:col-span-2 lg:col-span-4 my-3" /> | ||||
|                                 {% if user.membership_pdf %} | ||||
|                                     <a href="/admin/user/{{ user.id }}/membership" | ||||
|                                        class="text-black dark:text-white">Beitrittserklärung herunterladen</a> | ||||
|                                 {% else %} | ||||
|                                     {{ macros::input(label='Beitrittserklärung', name='membership_pdf', id=loop.index, type="file", readonly=allowed_to_edit == false, accept='application/pdf') }} | ||||
|                                 {% endif %} | ||||
|                                 {{ macros::input(label='DOB', name='dob', id=loop.index, type="text", value=user.dob, readonly=allowed_to_edit == false) }} | ||||
|                                 {{ macros::input(label='Weight (kg)', name='weight', id=loop.index, type="text", value=user.weight, readonly=allowed_to_edit == false) }} | ||||
|                                 {{ macros::input(label='Sex', name='sex', id=loop.index, type="text", value=user.sex, readonly=allowed_to_edit == false) }} | ||||
|                                 {{ macros::input(label='Mitglied seit', name='member_since_date', id=loop.index, type="text", value=user.member_since_date, readonly=allowed_to_edit == false) }} | ||||
|                                 {{ macros::input(label='Geburtsdatum', name='birthdate', id=loop.index, type="text", value=user.birthdate, readonly=allowed_to_edit == false) }} | ||||
|                                 {{ macros::input(label='Mail', name='mail', id=loop.index, type="text", value=user.mail, readonly=allowed_to_edit == false) }} | ||||
|                                 {{ macros::input(label='Nickname', name='nickname', id=loop.index, type="text", value=user.nickname, readonly=allowed_to_edit == false) }} | ||||
|                                 {{ macros::input(label='Notizen', name='notes', id=loop.index, type="text", value=user.notes, readonly=allowed_to_edit == false) }} | ||||
|                                 {{ macros::input(label='Telefon', name='phone', id=loop.index, type="text", value=user.phone, readonly=allowed_to_edit == false) }} | ||||
|                                 {{ macros::input(label='Adresse', name='address', id=loop.index, type="text", value=user.address, readonly=allowed_to_edit == false) }} | ||||
|                                 {% if allowed_to_edit %} | ||||
|                                     {{ macros::select(label="Familie", data=families, name='family_id', selected_id=user.family_id, display=['names'], default="Keine Familie", new_last_entry='Neue Familie anlegen') }} | ||||
|                                 {% endif %} | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         {% if allowed_to_edit %} | ||||
|                             <div class="mt-3 text-right"> | ||||
|                                 <a href="/admin/user/{{ user.id }}/delete" | ||||
|                                    class="w-28 btn btn-alert" | ||||
|                                    onclick="return confirm('Wirklich löschen?');"> | ||||
|                                     {% include "includes/delete-icon" %} | ||||
|                                     Löschen | ||||
|                                 </a> | ||||
|                                 <input value="Ändern" type="submit" class="w-28 btn btn-primary ml-1" /> | ||||
|                             </div> | ||||
|                         {% endif %} | ||||
|                     </form> | ||||
|                 </details> | ||||
|                  class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative flex justify-between items-center"> | ||||
|                 <span class="text-black dark:text-white"> | ||||
|                     <span class="font-bold"> | ||||
|                         {{ user.name }} | ||||
|                         {% if user.last_access %}• ⏳ {{ user.last_access | date }}{% endif %} | ||||
|                     </span> | ||||
|                     <small class="block text-gray-600 dark:text-gray-100"> | ||||
|                         {% for role in user.roles -%} | ||||
|                             {{ role }} | ||||
|                             {%- if not loop.last %}, | ||||
|                             {% endif -%} | ||||
|                         {% endfor %} | ||||
|                     </small> | ||||
|                 </span> | ||||
|                 <a href="/admin/user/{{ user.id }}" class="btn btn-dark ml-3">{% include "includes/pencil" %}</a> | ||||
|             </div> | ||||
|         {% endfor %} | ||||
|     </div> | ||||
|   | ||||
| @@ -3,10 +3,12 @@ | ||||
| {% extends "base" %} | ||||
| {% block content %} | ||||
|     <div class="max-w-screen-lg w-full"> | ||||
|         {% if "admin" in loggedin_user.roles or "Vorstand" in loggedin_user.roles %} | ||||
|             <a href="/admin/user" class="link link-primary link-no-underline">← Userverwaltung</a> | ||||
|         {% endif %} | ||||
|         <h1 class="h1">{{ user.name }}</h1> | ||||
|         <div class="grid sm:grid-cols-2 gap-3"> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||
|                  role="alert"> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> | ||||
|                 <h2 class="h2"> | ||||
|                     Grunddaten | ||||
|                     <br /> | ||||
| @@ -14,11 +16,11 @@ | ||||
|                         {% if user.last_access %} | ||||
|                             Zuletzt eingeloggt am {{ user.last_access | date(format="%d. %m. %Y") }} | ||||
|                         {% else %} | ||||
|                             {{ user.name }} hat sich noch nie eingeloggt. | ||||
|                             App-Boykott 😢 | ||||
|                         {% endif %} | ||||
|                     </small> | ||||
|                 </h2> | ||||
|                 <div class="mx-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|                 <div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|                     <div class="py-3 grid gap-3"> | ||||
|                         <form action="/admin/user/{{ user.id }}/change-mail" method="post"> | ||||
|                             {{ macros::inputgroup(label='Mailadresse', name='mail', type="text", value=user.mail, readonly=not allowed_to_edit) }} | ||||
| @@ -29,12 +31,23 @@ | ||||
|                         <form action="/admin/user/{{ user.id }}/change-nickname" method="post"> | ||||
|                             {{ macros::inputgroup(label='Spitzname', name='nickname', type="text", value=user.nickname, readonly=not allowed_to_edit) }} | ||||
|                         </form> | ||||
|                         {% if allowed_to_edit %} | ||||
|                         <form action="/admin/user/{{ user.id }}/new-note" method="post"> | ||||
|                             {{ macros::inputgroup(label='Neue Notiz', name='note', type="text") }} | ||||
|                         </form> | ||||
|                         {% endif %} | ||||
|                         <span>Notizen: to be replaced with activity :-)</span> | ||||
|                         {% if user.pw and allowed_to_edit %} | ||||
|                             <div> | ||||
|                                 <a class="block my-1 font-normal text-[#f43f5e] dark:text-primary-200 hover:text-primary-900 dark:hover:text-primary-300 underline" | ||||
|                                    href="/admin/user/{{ user.id }}/reset-pw" | ||||
|                                    onclick="return confirm('Willst du wirklich das Passwort zurücksetzen?');">Passwort zurücksetzen</a> | ||||
|                             </div> | ||||
|                         {% endif %} | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||
|                  role="alert"> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> | ||||
|                 <h2 class="h2"> | ||||
|                     Mitgliedschaft | ||||
|                     <br /> | ||||
| @@ -55,7 +68,7 @@ | ||||
|                         {% endif %} | ||||
|                     </small> | ||||
|                 </h2> | ||||
|                 <div class="mx-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|                 <div class="mx-3"> | ||||
|                     {% if is_clubmember %} | ||||
|                         <div class="py-3 grid gap-3"> | ||||
|                             <form action="/admin/user/{{ user.id }}/change-member-since" method="post"> | ||||
| @@ -73,7 +86,8 @@ | ||||
|                         </div> | ||||
|                         <div class="py-3"> | ||||
|                             {% if user.membership_pdf %} | ||||
|                                 <a href="/admin/user/{{ user.id }}/membership" class="link link-primary">Beitrittserklärung herunterladen</a> | ||||
|                                 <a href="/admin/user/{{ user.id }}/membership" | ||||
|                                    class="link link-primary link-no-underline">Beitrittserklärung herunterladen ↓</a> | ||||
|                             {% else %} | ||||
|                                 ⚠️ Aktuell gibt's keine Beitrittserklärung 😢 | ||||
|                                 {% if allowed_to_edit %} | ||||
| @@ -93,6 +107,9 @@ | ||||
|                         {% if allowed_to_edit %} | ||||
|                             <div class="py-3"> | ||||
|                                 <div class="mt-3 text-right"> | ||||
|                                     <button type="button" | ||||
|                                             onclick="document.getElementById('change-member-type').showModal()" | ||||
|                                             class="btn btn-dark">Mitgliedsstatus ändern</button> | ||||
|                                     <a href="/admin/user/{{ user.id }}/delete" | ||||
|                                        class="btn btn-alert" | ||||
|                                        onclick="return confirm('Ist {{ user.name }} wirklich aus dem Verein ausgetreten?');"> | ||||
| @@ -101,115 +118,241 @@ | ||||
|                                     </a> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <dialog id="change-member-type" | ||||
|                                     class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md" | ||||
|                                     onclick="document.getElementById('change-member-type').close()"> | ||||
|                                 <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                                     <button type="button" | ||||
|                                             onclick="document.getElementById('change-member-type').close()" | ||||
|                                             title="Schließen" | ||||
|                                             class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3"> | ||||
|                                         <svg class="inline h-5 w-5" | ||||
|                                              width="16" | ||||
|                                              height="16" | ||||
|                                              fill="currentColor" | ||||
|                                              viewBox="0 0 16 16"> | ||||
|                                             <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path> | ||||
|                                         </svg> | ||||
|                                     </button> | ||||
|                                     <div class="mt-8"> | ||||
|                                         <form action="/admin/user/{{ user.id }}/change-membertype" | ||||
|                                               method="post" | ||||
|                                               enctype="multipart/form-data" | ||||
|                                               class="grid gap-3"> | ||||
|                                             <div> | ||||
|                                                 <label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedsstatus</label> | ||||
|                                                 <select name="membertype" id="membertype" class="input rounded-md "> | ||||
|                                                     <option selected="" value="regular">Reguläres Vereinsmitglied</option> | ||||
|                                                     <option value="unterstuetzend">Unterstützendes Vereinsmitglied</option> | ||||
|                                                     <option value="foerdernd">Förderndes Vereinsmitglied</option> | ||||
|                                                 </select> | ||||
|                                             </div> | ||||
|                                             <input value="Ändern" type="submit" class="btn btn-primary" /> | ||||
|                                         </form> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </dialog> | ||||
|                         {% endif %} | ||||
|                     {% elif "Scheckbuch" in member %} | ||||
|                         <div class="grid gap-3 pb-3"> | ||||
|                             {% for log in logbook %} | ||||
|                                 {{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index, allowed_to_edit=false) }} | ||||
|                             {% endfor %} | ||||
|                             <button type="button" | ||||
|                                     onclick="document.getElementById('call-for-action').showModal()" | ||||
|                                     class="btn btn-primary">Zu Vereinsmitglied umwandeln</button> | ||||
|                         </div> | ||||
|                         <dialog id="call-for-action" | ||||
|                                 class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md" | ||||
|                                 onclick="document.getElementById('call-for-action').close()"> | ||||
|                             <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                                 <button type="button" | ||||
|                                         onclick="document.getElementById('call-for-action').close()" | ||||
|                                         title="Schließen" | ||||
|                                         class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3"> | ||||
|                                     <svg class="inline h-5 w-5" | ||||
|                                          width="16" | ||||
|                                          height="16" | ||||
|                                          fill="currentColor" | ||||
|                                          viewBox="0 0 16 16"> | ||||
|                                         <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path> | ||||
|                                     </svg> | ||||
|                                 </button> | ||||
|                                 <div class="mt-8"> | ||||
|                                     <form action="/admin/user/{{ user.id }}/scheckbook-to-regular" | ||||
|                                           method="post" | ||||
|                                           enctype="multipart/form-data" | ||||
|                                           class="grid gap-3"> | ||||
|                                         Type: Select -> normales Mitglied, förderndes Mitglied, unterstützendes Mitglied | ||||
|                                         {{ macros::input(label='Mitglied seit', name='member_since', type="date", value=now() | date(), required=true) }} | ||||
|                                         {{ macros::input(label='Geburtsdatum', name='birthdate', type="date", value=user.birthdate, required=true) }} | ||||
|                                         {{ macros::input(label='Telefonnummer', name='phone', type="text", value=user.phone, required=true) }} | ||||
|                                         {{ macros::input(label='Adresse', name='address', type="text", value=user.address, required=true) }} | ||||
|                                         {{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }} | ||||
|                                         <input value="Als neues, reguläres Mitglied anlegen" | ||||
|                                                type="submit" | ||||
|                                                class="btn btn-primary" /> | ||||
|                                     </form> | ||||
|                         {% if allowed_to_edit %} | ||||
|                             <div class="grid gap-3 pb-3"> | ||||
|                                 <div class="max-h-60 overflow-y-scroll"> | ||||
|                                     {% for log in logbook %} | ||||
|                                         {{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index, allowed_to_edit=false) }} | ||||
|                                     {% endfor %} | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </dialog> | ||||
|                         {% endif %} | ||||
|                     {% elif "SchnupperInterest" in member %} | ||||
|                         {% if allowed_to_edit %} | ||||
|                             <div class="grid pt-3"> | ||||
|                                 <a href="/admin/user/{{ user.id }}/schnupperinterest-to-scheckbuch" | ||||
|                                    class="btn btn-dark" | ||||
|                                    onclick="return confirm('Willst du \'{{ user.name }}\' wirklich auf ein Scheckbuch umwandeln?');">In Scheckbuch umwandeln</a> | ||||
|                             </div> | ||||
|                             <div class="grid pt-3"> | ||||
|                                 <a href="/admin/user/{{ user.id }}/schnupperinterest-to-schnupperant" | ||||
|                                    class="btn btn-dark" | ||||
|                                    onclick="return confirm('Hat sich \'{{ user.name }}\' wirklich zum Kurs angemeldet?');">Zum Schnupperkurs angemeldet</a> | ||||
|                             </div> | ||||
|                             <div class="grid pt-3"> | ||||
|                                 <a href="/admin/user/{{ user.id }}/delete" | ||||
|                                    class="btn btn-alert" | ||||
|                                    onclick="return confirm('Ist {{ user.name }} wirklich nicht mehr am Schnupperkurs interessiert?');"> | ||||
|                                     {% include "includes/delete-icon" %} | ||||
|                                     Daten löschen | ||||
|                                 </a> | ||||
|                             </div> | ||||
|                         {% endif %} | ||||
|                     {% elif "Schnupperant" in member %} | ||||
|                         {% if allowed_to_edit %} | ||||
|                             <div class="grid pt-3"> | ||||
|                                 <a href="/admin/user/{{ user.id }}/schnupperant-to-schnupperinterest" | ||||
|                                    class="btn btn-dark" | ||||
|                                    onclick="return confirm('Hat sich \'{{ user.name }}\' wirklich vom Schnupperkurs abgemeldet?');">Vom Kurs abgemeldet</a> | ||||
|                             </div> | ||||
|                             <div class="grid pt-3"> | ||||
|                                 <a href="/admin/user/{{ user.id }}/schnupperant-to-scheckbuch" | ||||
|                                    class="btn btn-dark" | ||||
|                                    onclick="return confirm('Willst du \'{{ user.name }}\' wirklich auf ein Scheckbuch umwandeln?');">In Scheckbuch umwandeln</a> | ||||
|                             </div> | ||||
|                             <div class="grid pt-3"> | ||||
|                                 <a href="/admin/user/{{ user.id }}/delete" | ||||
|                                    class="btn btn-alert" | ||||
|                                    onclick="return confirm('Ist {{ user.name }} wirklich nicht mehr am Schnupperkurs interessiert?');"> | ||||
|                                     {% include "includes/delete-icon" %} | ||||
|                                     Daten löschen | ||||
|                                 </a> | ||||
|                             </div> | ||||
|                         {% endif %} | ||||
|                     {% endif %} | ||||
|                     {% if "Scheckbuch" in member or "Schnupperant" in member %} | ||||
|                         {% if allowed_to_edit %} | ||||
|                             <div class="grid gap-3 pb-3 mt-3"> | ||||
|                                 <button type="button" | ||||
|                                         onclick="document.getElementById('call-for-action').showModal()" | ||||
|                                         class="btn btn-primary">Zu Vereinsmitglied umwandeln</button> | ||||
|                             </div> | ||||
|                             <dialog id="call-for-action" | ||||
|                                     class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md" | ||||
|                                     onclick="document.getElementById('call-for-action').close()"> | ||||
|                                 <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                                     <button type="button" | ||||
|                                             onclick="document.getElementById('call-for-action').close()" | ||||
|                                             title="Schließen" | ||||
|                                             class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3"> | ||||
|                                         <svg class="inline h-5 w-5" | ||||
|                                              width="16" | ||||
|                                              height="16" | ||||
|                                              fill="currentColor" | ||||
|                                              viewBox="0 0 16 16"> | ||||
|                                             <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path> | ||||
|                                         </svg> | ||||
|                                     </button> | ||||
|                                     <div class="mt-8"> | ||||
|                                         {% if "Scheckbuch" in member %} | ||||
|                                             {% set action = "scheckbook-to-regular" %} | ||||
|                                         {% elif "Schnupperant" in member %} | ||||
|                                             {% set action = "schnupperant-to-regular" %} | ||||
|                                         {% endif %} | ||||
|                                         <form action="/admin/user/{{ user.id }}/{{ action }}" | ||||
|                                               method="post" | ||||
|                                               enctype="multipart/form-data" | ||||
|                                               class="grid gap-3"> | ||||
|                                             <div> | ||||
|                                                 <label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label> | ||||
|                                                 <select name="membertype" id="membertype" class="input rounded-md "> | ||||
|                                                     <option selected="" value="regular">Reguläres Vereinsmitglied</option> | ||||
|                                                     <option value="unterstuetzend">Unterstützendes Vereinsmitglied</option> | ||||
|                                                     <option value="foerdernd">Förderndes Vereinsmitglied</option> | ||||
|                                                 </select> | ||||
|                                             </div> | ||||
|                                             {{ macros::input(label='Mitglied seit', name='member_since', type="date", value=now() | date(), required=true) }} | ||||
|                                             {{ macros::input(label='Geburtsdatum', name='birthdate', type="date", value=user.birthdate, required=true) }} | ||||
|                                             {{ macros::input(label='Telefonnummer', name='phone', type="text", value=user.phone, required=true) }} | ||||
|                                             {{ macros::input(label='Adresse', name='address', type="text", value=user.address, required=true) }} | ||||
|                                             {{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }} | ||||
|                                             <input value="Als neues, reguläres Mitglied anlegen" | ||||
|                                                    type="submit" | ||||
|                                                    class="btn btn-primary" /> | ||||
|                                         </form> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </dialog> | ||||
|                             <div class="grid pt-3"> | ||||
|                                 <a href="/admin/user/{{ user.id }}/delete" | ||||
|                                    class="btn btn-alert" | ||||
|                                    onclick="return confirm('Willst du die Daten von {{ user.name }} wirklich? Seine restlichen Scheckbuch-Ausfahrten entfallen damit...');"> | ||||
|                                     {% include "includes/delete-icon" %} | ||||
|                                     Daten löschen | ||||
|                                 </a> | ||||
|                             </div> | ||||
|                         {% endif %} | ||||
|                     {% endif %} | ||||
|                 </div> | ||||
|             </div> | ||||
|             {% if is_clubmember %} | ||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||
|                      role="alert"> | ||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> | ||||
|                     <h2 class="h2">Rollen</h2> | ||||
|                     <div class="mx-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|                         <div class="py-3"> | ||||
|                             <ul> | ||||
|                                 {% for role in user.proper_roles -%} | ||||
|                                     {% if not role.cluster and not role.hide_in_lists %} | ||||
|                                         <li class="flex my-2 w-full justify-between items-center hover:bg-gray-100"> | ||||
|                                             <span> | ||||
|                                                 <strong> | ||||
|                                                     {% if role.formatted_name %} | ||||
|                                                         {{ role.formatted_name }} | ||||
|                                                     {% else %} | ||||
|                                                         {{ role.name }} | ||||
|                                                     {% endif %} | ||||
|                                                 </strong> | ||||
|                                                 <br /> | ||||
|                                                 <small>{{ role.desc }}</small> | ||||
|                                             </span> | ||||
|                                             {% if allowed_to_edit %} | ||||
|                                                 <a href="/admin/user/{{ user.id }}/remove-role/{{ role.id }}" | ||||
|                                                    onclick="return confirm('Willst du die Rolle \'{{ role.name }}\' von {{ user.name }} wirklich entfernen?');">🗑️</a> | ||||
|                                             {% endif %} | ||||
|                                         </li> | ||||
|                                     {% endif %} | ||||
|                                 {% endfor %} | ||||
|                             </ul> | ||||
|                             {% if allowed_to_edit %} | ||||
|                                 <details> | ||||
|                                     <summary>+ Rolle</summary> | ||||
|                                     <form action="/admin/user/{{ user.id }}/add-role" method="post"> | ||||
|                                         <fieldset> | ||||
|                                             <select name="role_id"> | ||||
|                                                 {% for role in roles %} | ||||
|                                                     {% if not role.cluster and role not in user.proper_roles and not role.hide_in_lists %} | ||||
|                                                         <option value="{{ role.id }}"> | ||||
|                                                             {% if role.formatted_name %} | ||||
|                                                                 {{ role.formatted_name }} | ||||
|                                                             {% else %} | ||||
|                                                                 {{ role.name }} | ||||
|                                                             {% endif %} | ||||
|                                                             {% if role.desc %}({{ role.desc }}){% endif %} | ||||
|                                                         </option> | ||||
|                                                     {% endif %} | ||||
|                                                 {% endfor %} | ||||
|                                             </select> | ||||
|                                             <input value="Rolle hinzufügen" type="submit" class="btn btn-primary ml-1" /> | ||||
|                                         </fieldset> | ||||
|                                     </form> | ||||
|                                 </details> | ||||
|                             {% endif %} | ||||
|                         </div> | ||||
|                     <div> | ||||
|                         <ul class="divide-y divide-gray-200 dark:divide-primary-60 w-full"> | ||||
|                             {% for role in user.proper_roles -%} | ||||
|                                 {% if not role.cluster and not role.hide_in_lists %} | ||||
|                                     <li class="flex w-full justify-between items-center p-3 {% if allowed_to_edit %}hover:bg-gray-100 dark:hover:bg-primary-950{% endif %}"> | ||||
|                                         <span> | ||||
|                                             <strong> | ||||
|                                                 {% if role.formatted_name %} | ||||
|                                                     {{ role.formatted_name }} | ||||
|                                                 {% else %} | ||||
|                                                     {{ role.name }} | ||||
|                                                 {% endif %} | ||||
|                                             </strong> | ||||
|                                             <br /> | ||||
|                                             <small>{{ role.desc }}</small> | ||||
|                                         </span> | ||||
|                                         {% if allowed_to_edit %} | ||||
|                                             <a href="/admin/user/{{ user.id }}/remove-role/{{ role.id }}" | ||||
|                                                onclick="return confirm('Willst du die Rolle \'{{ role.name }}\' von {{ user.name }} wirklich entfernen?');">🗑️</a> | ||||
|                                         {% endif %} | ||||
|                                     </li> | ||||
|                                 {% endif %} | ||||
|                             {% endfor %} | ||||
|                         </ul> | ||||
|                         {% if allowed_to_edit %} | ||||
|                             <div class="m-3"> | ||||
|                                 <button type="button" | ||||
|                                         onclick="document.getElementById('role-modal').showModal()" | ||||
|                                         class="btn btn-primary w-full">Rolle hinzufügen</button> | ||||
|                             </div> | ||||
|                             <dialog id="role-modal" | ||||
|                                     class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md" | ||||
|                                     onclick="document.getElementById('role-modal').close()"> | ||||
|                                 <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                                     <button type="button" | ||||
|                                             onclick="document.getElementById('role-modal').close()" | ||||
|                                             title="Schließen" | ||||
|                                             class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3"> | ||||
|                                         <svg class="inline h-5 w-5" | ||||
|                                              width="16" | ||||
|                                              height="16" | ||||
|                                              fill="currentColor" | ||||
|                                              viewBox="0 0 16 16"> | ||||
|                                             <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path> | ||||
|                                         </svg> | ||||
|                                     </button> | ||||
|                                     <div class="mt-8"> | ||||
|                                         <form action="/admin/user/{{ user.id }}/add-role" | ||||
|                                               method="post" | ||||
|                                               class="grid gap-3"> | ||||
|                                             <div> | ||||
|                                                 <label for="role_id" class="text-sm text-gray-600 dark:text-gray-100">Rollen</label> | ||||
|                                                 <select name="role_id" id="role_id"  class="input rounded-md "> | ||||
|                                                     {% for role in roles %} | ||||
|                                                         {% if not role.cluster and role not in user.proper_roles and not role.hide_in_lists %} | ||||
|                                                             <option value="{{ role.id }}"> | ||||
|                                                                 {% if role.formatted_name %} | ||||
|                                                                     {{ role.formatted_name }} | ||||
|                                                                 {% else %} | ||||
|                                                                     {{ role.name }} | ||||
|                                                                 {% endif %} | ||||
|                                                             </option> | ||||
|                                                         {% endif %} | ||||
|                                                     {% endfor %} | ||||
|                                                 </select> | ||||
|                                             </div> | ||||
|                                             <input value="Rolle hinzufügen" type="submit" class="btn btn-primary" /> | ||||
|                                         </form> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </dialog> | ||||
|                         {% endif %} | ||||
|                     </div> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|             {% if supposed_to_pay %} | ||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||
|                      role="alert"> | ||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> | ||||
|                     <h2 class="h2">💸-Beitrag</h2> | ||||
|                     <div class="mx-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|                     <div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|                         <div class="py-3"> | ||||
|                             {% if fee %} | ||||
|                                 <div> | ||||
| @@ -242,10 +385,9 @@ | ||||
|                     </div> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||
|                  role="alert"> | ||||
|                 <h2 class="h2">Aktivität von und mit {{ user.name }}</h2> | ||||
|                 <div class="mx-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> | ||||
|                 <h2 class="h2">Aktivitäten</h2> | ||||
|                 <div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|                     <div class="py-3"> | ||||
|                         <ul class="list-disc ms-4"> | ||||
|                             <li>Passwort zurückgesetzt am/um X</li> | ||||
| @@ -254,8 +396,7 @@ | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||
|                  role="alert"> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> | ||||
|                 <h2 class="h2">TODO</h2> | ||||
|                 <div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative"> | ||||
|                     <span class="text-black dark:text-white cursor-pointer"> | ||||
| @@ -277,11 +418,6 @@ | ||||
|                           method="post" | ||||
|                           enctype="multipart/form-data" | ||||
|                           class="w-full mt-2"> | ||||
|                         {% if user.pw %} | ||||
|                             <a class="block my-1 font-normal text-[#f43f5e] dark:text-primary-200 hover:text-primary-900 dark:hover:text-primary-300 underline" | ||||
|                                href="/admin/user/{{ user.id }}/reset-pw" | ||||
|                                onclick="return confirm('Willst du wirklich das Passwort zurücksetzen?');">Passwort zurücksetzen</a> | ||||
|                         {% endif %} | ||||
|                         <div class="w-full grid gap-3 mt-3"> | ||||
|                             <input type="hidden" name="id" value="{{ user.id }}" /> | ||||
|                             <div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-3"> | ||||
| @@ -318,10 +454,9 @@ | ||||
|                     </form> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||
|                  role="alert"> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> | ||||
|                 <h2 class="h2">Ergo-Challenge</h2> | ||||
|                 <div class="mx-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|                 <div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> | ||||
|                     <div class="py-3"> | ||||
|                         {{ macros::input(label='DOB', name='dob', type="text", value=user.dob, readonly=allowed_to_edit == false) }} | ||||
|                         {{ macros::input(label='Weight (kg)', name='weight', type="text", value=user.weight, readonly=allowed_to_edit == false) }} | ||||
|   | ||||
| @@ -198,7 +198,10 @@ function setChoiceByLabel(choicesInstance, label) { | ||||
|                    {% if pattern %}pattern="{{ pattern }}"{% endif %} | ||||
|                    readonly /> | ||||
|             {% if allowed_to_edit %} | ||||
|                 <button type="button" class="btn btn-dark rounded-l-none-important edit-js">{% include "includes/pencil" %}</button> | ||||
|                 <button type="button" class="btn btn-dark rounded-l-none-important edit-js"> | ||||
|                     {% include "includes/pencil" %} | ||||
|                     <span class="sr-only">Bearbeiten</span> | ||||
|                 </button> | ||||
|                 <input value="x" | ||||
|                        type="reset" | ||||
|                        class="edit-js btn btn-alert btn-hidden rounded-none-important" /> | ||||
| @@ -244,7 +247,10 @@ function setChoiceByLabel(choicesInstance, label) { | ||||
|                 {% if new_last_entry %}<option value="-1">{{ new_last_entry }}</option>{% endif %} | ||||
|             </select> | ||||
|             {% if allowed_to_edit %} | ||||
|                 <button type="button" class="btn btn-dark rounded-l-none-important edit-js">{% include "includes/pencil" %}</button> | ||||
|                 <button type="button" class="btn btn-dark rounded-l-none-important edit-js"> | ||||
|                     {% include "includes/pencil" %} | ||||
|                     <span class="sr-only">Bearbeiten</span> | ||||
|                 </button> | ||||
|                 <input value="x" | ||||
|                        type="reset" | ||||
|                        class="edit-js btn btn-alert btn-hidden rounded-none-important" /> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user