8 Commits

Author SHA1 Message Date
gitea-actions
afc2a34c3e Update Cargo dependencies
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy-staging (push) Has been cancelled
CI/CD Pipeline / deploy-main (push) Has been cancelled
2026-02-27 02:19:17 +00:00
b2157a31c5 Merge pull request 'user merging should only be done by admins' (#1182) from user-upd into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 17m44s
CI/CD Pipeline / deploy-staging (push) Successful in 7m57s
CI/CD Pipeline / deploy-main (push) Has been skipped
Update Cargo Dependencies / update-dependencies (push) Successful in 1m43s
Reviewed-on: #1182
2026-01-08 20:23:08 +01:00
bfb3ae4b6e Merge pull request 'user-upd' (#1180) from user-upd into staging
Some checks failed
CI/CD Pipeline / deploy-staging (push) Has been cancelled
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy-main (push) Has been cancelled
Reviewed-on: #1180
2026-01-08 20:15:41 +01:00
88a3e5f2d0 Merge pull request 'Merge pull request 'force an action on important notifications' (#1177) from force-action-on-important-notifications into main' (#1178) from new-sort-option into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 17m44s
CI/CD Pipeline / deploy-staging (push) Successful in 7m49s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #1178
2026-01-05 13:09:12 +01:00
32c250536d Merge pull request 'force-action-on-important-notifications' (#1176) from force-action-on-important-notifications into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 17m51s
CI/CD Pipeline / deploy-staging (push) Successful in 7m56s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #1176
2026-01-03 22:11:55 +01:00
b9d0e2a2dc Merge pull request 'show-full-stats' (#1172) from show-full-stats into staging
Some checks failed
CI/CD Pipeline / deploy-staging (push) Has been cancelled
CI/CD Pipeline / deploy-main (push) Has been cancelled
CI/CD Pipeline / test (push) Has been cancelled
Reviewed-on: #1172
2026-01-03 21:49:14 +01:00
b597898bdf Merge pull request 'manual-deploy' (#1174) from manual-deploy into staging
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy-staging (push) Has been cancelled
CI/CD Pipeline / deploy-main (push) Has been cancelled
Reviewed-on: #1174
2026-01-03 21:43:02 +01:00
e90555214a Merge pull request 'No need to pay for deleted boats' (#1169) from handle-deleted-boats into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 19m20s
CI/CD Pipeline / deploy-staging (push) Successful in 26m29s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #1169
2026-01-03 14:04:39 +01:00
7 changed files with 863 additions and 1128 deletions

1614
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,11 +23,11 @@ tera = { version = "1.20", features = ["date-locale"], optional = true}
ics = "0.5"
futures = "0.3"
lettre = "0.11"
csv = "1.3"
csv = "1.4"
itertools = "0.14"
job_scheduler_ng = "2.2"
ureq = { version = "3.0", features = ["json"] }
regex = "1.11"
job_scheduler_ng = "2.4"
ureq = { version = "3.2", features = ["json"] }
regex = "1.12"
urlencoding = "2.1"
[target.'cfg(not(windows))'.dependencies]

View File

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

View File

@@ -34,7 +34,6 @@ 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

@@ -1,233 +0,0 @@
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,9 +8,8 @@ use crate::{
role::Role,
user::{
clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member,
nomembership::NoMembershipUser, regular::RegularUser, scheckbuch::ScheckbuchUser,
schnupperant::SchnupperantUser, schnupperinterest::SchnupperInterestUser,
unterstuetzend::UnterstuetzendUser,
regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser,
schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser,
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
},
@@ -1142,115 +1141,6 @@ 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,
@@ -1664,7 +1554,6 @@ 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,8 +73,6 @@
Förderndes Vereinsmitglied
{% elif "Unterstuetzend" in member %}
Unterstützendes Vereinsmitglied
{% elif "NoMembership" in member %}
⚠️ Kein Mitgliedsstatus!
{% endif %}
</small>
</h2>
@@ -230,19 +228,8 @@
</a>
</div>
{% endif %}
{% elif "NoMembership" in member %}
{% if allowed_to_edit %}
<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 löschen?');">
{% include "includes/delete-icon" %}
Daten löschen
</a>
</div>
{% endif %}
{% endif %}
{% if "Scheckbuch" in member or "Schnupperant" in member or "NoMembership" in member %}
{% if "Scheckbuch" in member or "Schnupperant" in member %}
{% if allowed_to_edit %}
<div class="grid gap-3 pb-3 mt-3">
<button type="button"
@@ -270,8 +257,6 @@
{% 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"