fix-user #1193

Merged
philipp merged 3 commits from fix-user into staging 2026-03-16 10:00:59 +01:00
5 changed files with 354 additions and 4 deletions

View File

@@ -14,6 +14,7 @@ pub(crate) enum Member {
Regular(User),
Foerdernd(User),
Unterstuetzend(User),
NoMembership(User),
}
impl Member {
@@ -31,7 +32,7 @@ impl Member {
} else if user.has_role(db, "Unterstützend").await {
Self::Unterstuetzend(user)
} else {
panic!("User {user} has no membership_type!!");
Self::NoMembership(user)
}
}

View File

@@ -34,6 +34,7 @@ mod fee;
pub(crate) mod foerdernd;
pub(crate) mod member;
pub mod merge;
pub(crate) mod nomembership;
pub(crate) mod regular;
pub(crate) mod scheckbuch;
pub(crate) mod schnupperant;

View File

@@ -0,0 +1,233 @@
use super::foerdernd::FoerderndUser;
use super::regular::RegularUser;
use super::scheckbuch::ScheckbuchUser;
use super::unterstuetzend::UnterstuetzendUser;
use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder;
use crate::model::role::Role;
use crate::model::notification::Notification;
use chrono::NaiveDate;
use rocket::fs::TempFile;
use sqlx::SqlitePool;
use std::fmt::Display;
use std::ops::Deref;
pub(crate) struct NoMembershipUser {
pub(crate) user: User,
}
impl Deref for NoMembershipUser {
type Target = User;
fn deref(&self) -> &Self::Target {
&self.user
}
}
impl Display for NoMembershipUser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.user.name)
}
}
impl NoMembershipUser {
pub(crate) async fn new(db: &SqlitePool, user: &User) -> Option<Self> {
if ScheckbuchUser::new(db, user).await.is_some() {
return None;
}
if user.has_role(db, "schnupper-interessierte").await {
return None;
}
if user.has_role(db, "schnupperant").await {
return None;
}
if user.has_role(db, "Donau Linz").await {
return None;
}
if user.has_role(db, "Förderndes Mitglied").await {
return None;
}
if user.has_role(db, "Unterstützend").await {
return None;
}
Some(Self { user: user.clone() })
}
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?;
let regular = Role::find_by_name(db, "Donau Linz").await.unwrap();
self.user.add_role(db, changed_by, &regular).await?;
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, {} hatte keinen Mitgliedsstatus und ist nun seit {} ein neues reguläres Mitglied. 🎉",
self.name,
member_since
),
"Neues Vereinsmitglied",
None,
None,
)
.await;
ActivityBuilder::new(&format!(
"{changed_by} hat den User ohne Mitgliedsstatus {self} auf ein reguläres Mitglied upgegraded! Die Steuerpersonen wurden via Notification informiert."
))
.user(&self)
.save(db)
.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> {
self.set_data_for_clubmember(
db,
changed_by,
member_since,
birthdate,
phone,
address,
membership_pdf,
)
.await?;
let unterstuetzend = Role::find_by_name(db, "Unterstützend").await.unwrap();
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 keinen Mitgliedsstatus und ist nun seit {} ein neues unterstützendes Mitglied.",
self.name,
member_since
),
"Neues unterstützendes Vereinsmitglied",
None,
None,
)
.await;
}
ActivityBuilder::new(&format!(
"{changed_by} hat den User ohne Mitgliedsstatus {self} auf ein unterstützendes Mitglied upgegraded!"
))
.user(&self)
.save(db)
.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> {
self.set_data_for_clubmember(
db,
changed_by,
member_since,
birthdate,
phone,
address,
membership_pdf,
)
.await?;
let foerdernd = Role::find_by_name(db, "Förderndes Mitglied").await.unwrap();
self.user.add_role(db, changed_by, &foerdernd).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 keinen Mitgliedsstatus und ist nun seit {} ein neues förderndes Mitglied.",
self.name,
member_since
),
"Neues förderndes Vereinsmitglied",
None,
None,
)
.await;
}
ActivityBuilder::new(&format!(
"{changed_by} hat den User ohne Mitgliedsstatus {self} auf ein förderndes Mitglied upgegraded!"
))
.user(&self)
.save(db)
.await;
Ok(())
}
}

View File

@@ -8,8 +8,9 @@ use crate::{
role::Role,
user::{
clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member,
regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser,
schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser,
nomembership::NoMembershipUser, regular::RegularUser, scheckbuch::ScheckbuchUser,
schnupperant::SchnupperantUser, schnupperinterest::SchnupperInterestUser,
unterstuetzend::UnterstuetzendUser,
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
},
@@ -1141,6 +1142,115 @@ async fn scheckbook_to_regular(
}
}
#[post("/user/<id>/nomembership-to-regular", data = "<data>")]
async fn nomembership_to_regular(
db: &State<SqlitePool>,
data: Form<ScheckToRegularForm<'_>>,
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) = NoMembershipUser::new(db, &user).await else {
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
"User hat keinen fehlenden Mitgliedsstatus",
);
};
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 ChangeMembertypeForm {
membertype: String,
@@ -1554,6 +1664,7 @@ pub fn routes() -> Vec<Route> {
remove_role,
// Moves
scheckbook_to_regular,
nomembership_to_regular,
schnupperant_to_regular,
schnupperant_to_scheckbook,
schnupperinterest_to_schnupperant,

View File

@@ -73,6 +73,8 @@
Förderndes Vereinsmitglied
{% elif "Unterstuetzend" in member %}
Unterstützendes Vereinsmitglied
{% elif "NoMembership" in member %}
⚠️ Kein Mitgliedsstatus!
{% endif %}
</small>
</h2>
@@ -229,7 +231,7 @@
</div>
{% endif %}
{% endif %}
{% if "Scheckbuch" in member or "Schnupperant" in member %}
{% if "Scheckbuch" in member or "Schnupperant" in member or "NoMembership" in member %}
{% if allowed_to_edit %}
<div class="grid gap-3 pb-3 mt-3">
<button type="button"
@@ -257,6 +259,8 @@
{% set action = "scheckbook-to-regular" %}
{% elif "Schnupperant" in member %}
{% set action = "schnupperant-to-regular" %}
{% elif "NoMembership" in member %}
{% set action = "nomembership-to-regular" %}
{% endif %}
<form action="/admin/user/{{ user.id }}/{{ action }}"
method="post"