single-user-edit-page #971

Merged
philipp merged 23 commits from single-user-edit-page into staging 2025-05-03 19:20:21 +02:00
7 changed files with 408 additions and 176 deletions
Showing only changes of commit e6895c8cf1 - Show all commits

View File

@ -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(())
}

View File

@ -37,6 +37,7 @@ 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)]

View File

@ -98,6 +98,12 @@ impl SchnupperantUser {
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?;
@ -115,6 +121,38 @@ impl SchnupperantUser {
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,
@ -143,6 +181,11 @@ impl SchnupperantUser {
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
@ -195,6 +238,11 @@ impl SchnupperantUser {
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?;
@ -218,14 +266,9 @@ impl SchnupperantUser {
}
// TODO: make private
pub(crate) async fn notify(
&self,
db: &SqlitePool,
mail: &str,
smtp_pw: &str,
) -> Result<(), String> {
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, mail, smtp_pw).await?;
self.send_welcome_mail_to_user(db, smtp_pw).await?;
Ok(())
}
@ -233,13 +276,17 @@ impl SchnupperantUser {
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(format!(
"Couldn't send mail, because user {self} has no mail"
));
};
Mail::send_single(
db,
mail,
"ASKÖ Ruderverein Donau Linz | Dein Scheckbuch wartet auf Dich",
&mail,
"ASKÖ Ruderverein Donau Linz | Anmeldung Schnupperkurs",
format!(
"Hallo {0},

View 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;
}
}
}

View File

@ -8,9 +8,9 @@ use crate::{
role::Role,
user::{
clubmember::ClubMemberUser, member::Member, scheckbuch::ScheckbuchUser,
schnupperant::SchnupperantUser, AdminUser, AllowedToEditPaymentStatusUser,
ManageUserUser, User, UserWithDetails, UserWithMembershipPdf,
UserWithRolesAndMembershipPdf, VorstandUser,
schnupperant::SchnupperantUser, schnupperinterest::SchnupperInterestUser, AdminUser,
AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
},
},
tera::Config,
@ -765,56 +765,6 @@ 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,
@ -1130,6 +1080,94 @@ async fn schnupperant_to_scheckbook(
}
}
#[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",
),
Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", id)), e),
}
}
pub fn routes() -> Vec<Route> {
routes![
index,
@ -1139,7 +1177,6 @@ pub fn routes() -> Vec<Route> {
update,
create,
//create_scheckbuch,
//schnupper_to_scheckbuch,
delete,
fees,
fees_paid,
@ -1161,6 +1198,9 @@ pub fn routes() -> Vec<Route> {
scheckbook_to_regular,
schnupperant_to_regular,
schnupperant_to_scheckbook,
schnupperinterest_to_schnupperant,
schnupperant_to_schnupperinterest,
schnupperinterest_to_scheckbuch,
change_membertype,
]
}

View File

@ -4,8 +4,7 @@
{% 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">&larr; Userverwaltung</a>
<a href="/admin/user" class="link link-primary link-no-underline">&larr; Userverwaltung</a>
{% endif %}
<h1 class="h1">{{ user.name }}</h1>
<div class="grid sm:grid-cols-2 gap-3">
@ -82,7 +81,8 @@
</div>
<div class="py-3">
{% if user.membership_pdf %}
<a href="/admin/user/{{ user.id }}/membership" class="link link-primary link-no-underline">Beitrittserklärung herunterladen &darr;</a>
<a href="/admin/user/{{ user.id }}/membership"
class="link link-primary link-no-underline">Beitrittserklärung herunterladen &darr;</a>
{% else %}
⚠️ Aktuell gibt's keine Beitrittserklärung 😢
{% if allowed_to_edit %}
@ -142,9 +142,7 @@
<option value="foerdernd">Förderndes Vereinsmitglied</option>
</select>
</div>
<input value="Ändern"
type="submit"
class="btn btn-primary" />
<input value="Ändern" type="submit" class="btn btn-primary" />
</form>
</div>
</div>
@ -160,15 +158,49 @@
</div>
</div>
{% 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-scheckbuch" class="btn btn-dark"
<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">
@ -194,11 +226,10 @@
</button>
<div class="mt-8">
{% if "Scheckbuch" in member %}
{% set action = 'scheckbook-to-regular' %}
{% set action = "scheckbook-to-regular" %}
{% elif "Schnupperant" in member %}
{% set action = 'schnupperant-to-regular' %}
{% set action = "schnupperant-to-regular" %}
{% endif %}
<form action="/admin/user/{{ user.id }}/{{ action }}"
method="post"
enctype="multipart/form-data"
@ -223,6 +254,14 @@
</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>