forked from Ruderverein-Donau-Linz/rowt
Be able to update financial and skill; Fixes #974
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
use std::{fmt::Display, ops::DerefMut};
|
||||
use std::{cmp::Ordering, fmt::Display, ops::DerefMut};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
||||
@ -13,6 +13,30 @@ pub struct Role {
|
||||
pub(crate) cluster: Option<String>,
|
||||
}
|
||||
|
||||
// Implement PartialEq to compare roles based only on id
|
||||
impl PartialEq for Role {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Eq to indicate that equality is reflexive
|
||||
impl Eq for Role {}
|
||||
|
||||
// Implement PartialOrd if you need to sort or compare roles
|
||||
impl PartialOrd for Role {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.id.cmp(&other.id))
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Ord if you need total ordering (for sorting)
|
||||
impl Ord for Role {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.id.cmp(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Role {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name)
|
||||
@ -30,6 +54,27 @@ impl Role {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn all_cluster(db: &SqlitePool, cluster: &str) -> Vec<Role> {
|
||||
sqlx::query_as!(
|
||||
Role,
|
||||
r#"SELECT id,
|
||||
CASE WHEN formatted_name IS NOT NULL AND formatted_name != ''
|
||||
THEN formatted_name
|
||||
ELSE name
|
||||
END AS "name!: String",
|
||||
'' as formatted_name,
|
||||
desc,
|
||||
hide_in_lists,
|
||||
cluster
|
||||
FROM role
|
||||
WHERE cluster = ?"#,
|
||||
cluster
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn find_by_id(db: &SqlitePool, name: i32) -> Option<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
@ -59,21 +104,6 @@ WHERE id like ?
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn find_by_cluster_tx(db: &mut Transaction<'_, Sqlite>, name: i32) -> Option<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"
|
||||
SELECT id, name, formatted_name, desc, hide_in_lists, cluster
|
||||
FROM role
|
||||
WHERE cluster = ?
|
||||
",
|
||||
name
|
||||
)
|
||||
.fetch_one(db.deref_mut())
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn find_by_name(db: &SqlitePool, name: &str) -> Option<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
|
@ -1,7 +1,10 @@
|
||||
// TODO: put back in `src/model/user/mod.rs` once that is cleaned up
|
||||
|
||||
use super::{AllowedToEditPaymentStatusUser, ManageUserUser, User};
|
||||
use crate::model::{activity::Activity, family::Family, log::Log, mail::valid_mails, role::Role};
|
||||
use crate::model::{
|
||||
activity::Activity, family::Family, log::Log, mail::valid_mails, notification::Notification,
|
||||
role::Role,
|
||||
};
|
||||
use chrono::NaiveDate;
|
||||
use rocket::{fs::TempFile, tokio::io::AsyncReadExt};
|
||||
use sqlx::SqlitePool;
|
||||
@ -228,6 +231,103 @@ impl User {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn change_skill(
|
||||
&self,
|
||||
db: &SqlitePool,
|
||||
updated_by: &ManageUserUser,
|
||||
skill: Option<Role>,
|
||||
) -> Result<(), String> {
|
||||
let old_skill = self.skill(db).await;
|
||||
|
||||
let member = Role::find_by_name(db, "Donau Linz").await.unwrap();
|
||||
let cox = Role::find_by_name(db, "cox").await.unwrap();
|
||||
let bootsfuehrer = Role::find_by_name(db, "Bootsführer").await.unwrap();
|
||||
|
||||
match (old_skill, skill) {
|
||||
(old, new) if old == None && new == Some(cox.clone()) => {
|
||||
self.add_role(db, updated_by, &cox).await?;
|
||||
Notification::create_for_role(
|
||||
db,
|
||||
&member,
|
||||
&format!(
|
||||
"Liebes Vereinsmitglied, {self} ist ab sofort Steuerperson 🎉 Hip hip ...!"
|
||||
),
|
||||
"Neue Steuerperson",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
(old, new) if old == Some(cox.clone()) && new == Some(bootsfuehrer.clone()) => {
|
||||
self.remove_role(db, updated_by, &cox).await?;
|
||||
self.add_role(db, updated_by, &bootsfuehrer).await?;
|
||||
Notification::create_for_role(
|
||||
db,
|
||||
&member,
|
||||
&format!(
|
||||
"Liebes Vereinsmitglied, {self} ist ab sofort Bootsführer:in 🎉 Hip hip ...!"
|
||||
),
|
||||
"Neue:r Bootsführer:in",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
(old, new) if new == None => {
|
||||
if let Some(old) = old {
|
||||
self.remove_role(db, updated_by, &old).await?;
|
||||
let vorstand = Role::find_by_name(db, "Vorstand").await.unwrap();
|
||||
Notification::create_for_role(
|
||||
db,
|
||||
&vorstand,
|
||||
&format!("Lieber Vorstand, {self} ist ab kein {old} mehr."),
|
||||
"Steuerperson --",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
(old, new) => return Err(format!("Not allowed to change from {old:?} to {new:?}")),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn change_financial(
|
||||
&self,
|
||||
db: &SqlitePool,
|
||||
updated_by: &ManageUserUser,
|
||||
financial: Option<Role>,
|
||||
) -> Result<(), String> {
|
||||
let mut new = String::new();
|
||||
let mut old = String::new();
|
||||
|
||||
if let Some(old_financial) = self.financial(db).await {
|
||||
self.remove_role(db, updated_by, &old_financial).await?;
|
||||
old.push_str(&old_financial.name);
|
||||
} else {
|
||||
old.push_str("Keine Ermäßigung");
|
||||
}
|
||||
|
||||
if let Some(new_financial) = financial {
|
||||
self.add_role(db, updated_by, &new_financial).await?;
|
||||
new.push_str(&new_financial.name);
|
||||
} else {
|
||||
new.push_str("Keine Ermäßigung");
|
||||
}
|
||||
|
||||
Activity::create(
|
||||
db,
|
||||
&format!("({updated_by}) Ermäßigung von {self} von {old} auf {new} geändert"),
|
||||
&format!("user-{};", self.id),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_role(
|
||||
&self,
|
||||
db: &SqlitePool,
|
||||
|
@ -259,6 +259,39 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
||||
.into_iter().map(|r| r.name).collect()
|
||||
}
|
||||
|
||||
pub async fn financial(&self, db: &SqlitePool) -> Option<Role> {
|
||||
sqlx::query_as!(
|
||||
Role,
|
||||
"
|
||||
SELECT r.id, r.name, r.formatted_name, r.desc, r.hide_in_lists, r.cluster
|
||||
FROM role r
|
||||
JOIN user_role ur ON r.id = ur.role_id
|
||||
WHERE ur.user_id = ?
|
||||
AND r.cluster = 'financial';
|
||||
",
|
||||
self.id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
pub async fn skill(&self, db: &SqlitePool) -> Option<Role> {
|
||||
sqlx::query_as!(
|
||||
Role,
|
||||
"
|
||||
SELECT r.id, r.name, r.formatted_name, r.desc, r.hide_in_lists, r.cluster
|
||||
FROM role r
|
||||
JOIN user_role ur ON r.id = ur.role_id
|
||||
WHERE ur.user_id = ?
|
||||
AND r.cluster = 'skill';
|
||||
",
|
||||
self.id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn real_roles(&self, db: &SqlitePool) -> Vec<Role> {
|
||||
sqlx::query_as!(
|
||||
Role,
|
||||
|
@ -130,6 +130,10 @@ async fn view(
|
||||
let member = Member::from(db, user.clone()).await;
|
||||
let fee = user.fee(db).await;
|
||||
let activities = Activity::for_user(db, &user).await;
|
||||
let financial = Role::all_cluster(db, "financial").await;
|
||||
let user_financial = user.financial(db).await;
|
||||
let skill = Role::all_cluster(db, "skill").await;
|
||||
let user_skill = user.skill(db).await;
|
||||
|
||||
let user = UserWithRolesAndMembershipPdf::from_user(db, user).await;
|
||||
|
||||
@ -148,6 +152,10 @@ async fn view(
|
||||
context.insert("is_clubmember", &member.is_club_member());
|
||||
context.insert("supposed_to_pay", &member.supposed_to_pay());
|
||||
context.insert("fee", &fee);
|
||||
context.insert("skill", &skill);
|
||||
context.insert("user_skill", &user_skill);
|
||||
context.insert("financial", &financial);
|
||||
context.insert("user_financial", &user_financial);
|
||||
context.insert("member", &member);
|
||||
context.insert("activities", &activities);
|
||||
context.insert("roles", &roles);
|
||||
@ -456,6 +464,86 @@ async fn update_family(
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(FromForm, Debug)]
|
||||
pub struct ChangeSkillForm {
|
||||
skill_id: String,
|
||||
}
|
||||
|
||||
#[post("/user/<id>/change-skill", data = "<data>")]
|
||||
async fn change_skill(
|
||||
db: &State<SqlitePool>,
|
||||
data: Form<ChangeSkillForm>,
|
||||
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 skill = if &data.skill_id == "" {
|
||||
None
|
||||
} else {
|
||||
let Ok(skill_id) = data.skill_id.parse() else {
|
||||
return Flash::error(
|
||||
Redirect::to(format!("/admin/user/{id}")),
|
||||
format!("Skill_id is not a number"),
|
||||
);
|
||||
};
|
||||
Role::find_by_id(db, skill_id).await
|
||||
};
|
||||
|
||||
match user.change_skill(db, &admin, skill).await {
|
||||
Ok(()) => Flash::success(
|
||||
Redirect::to(format!("/admin/user/{}", user.id)),
|
||||
"Skill erfolgreich geändert",
|
||||
),
|
||||
Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", user.id)), e),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromForm, Debug)]
|
||||
pub struct ChangeFinancialForm {
|
||||
financial_id: String,
|
||||
}
|
||||
|
||||
#[post("/user/<id>/change-financial", data = "<data>")]
|
||||
async fn change_financial(
|
||||
db: &State<SqlitePool>,
|
||||
data: Form<ChangeFinancialForm>,
|
||||
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 financial = if &data.financial_id == "" {
|
||||
None
|
||||
} else {
|
||||
let Ok(financial_id) = data.financial_id.parse() else {
|
||||
return Flash::error(
|
||||
Redirect::to(format!("/admin/user/{id}")),
|
||||
format!("Finacial_id is not a number"),
|
||||
);
|
||||
};
|
||||
Role::find_by_id(db, financial_id).await
|
||||
};
|
||||
|
||||
match user.change_financial(db, &admin, financial).await {
|
||||
Ok(()) => Flash::success(
|
||||
Redirect::to(format!("/admin/user/{}", user.id)),
|
||||
"Ermäßigung erfolgreich geändert",
|
||||
),
|
||||
Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", user.id)), e),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromForm, Debug)]
|
||||
pub struct AddMembershipPDFForm<'a> {
|
||||
membership_pdf: TempFile<'a>,
|
||||
@ -1176,6 +1264,8 @@ pub fn routes() -> Vec<Route> {
|
||||
update_birthdate,
|
||||
update_address,
|
||||
update_family,
|
||||
change_skill,
|
||||
change_financial,
|
||||
add_membership_pdf,
|
||||
add_role,
|
||||
add_note,
|
||||
|
Reference in New Issue
Block a user