Merge pull request 'reflect new fee structure' (#1029) from new-fee-structure into main
All checks were successful
CI/CD Pipeline / test (push) Successful in 15m47s
CI/CD Pipeline / deploy-staging (push) Has been skipped
CI/CD Pipeline / deploy-main (push) Successful in 7m31s

Reviewed-on: #1029
This commit is contained in:
philipp 2025-05-17 08:14:04 +02:00
commit 7e10253e2e
7 changed files with 116 additions and 65 deletions

View File

@ -23,6 +23,9 @@ pub(crate) const UNTERSTUETZEND: i64 = 2500;
pub(crate) const FOERDERND: i64 = 8500;
pub(crate) const SCHECKBUCH: i64 = 3000;
pub(crate) const EINSCHREIBGEBUEHR: i64 = 3000;
pub(crate) const DUAL_MEMBERSHIP: i64 = 18000;
pub(crate) const TRIAL_ROWING: i64 = 12000;
pub(crate) const TRIAL_ROWING_REDUCED: i64 = 6000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NonEmptyString(String);

View File

@ -1,9 +1,9 @@
use std::{error::Error, fs};
use lettre::{
Address, Message, SmtpTransport, Transport,
message::{Attachment, MultiPart, SinglePart, header::ContentType},
message::{header::ContentType, Attachment, MultiPart, SinglePart},
transport::smtp::authentication::Credentials,
Address, Message, SmtpTransport, Transport,
};
use sqlx::{Sqlite, SqlitePool, Transaction};
@ -161,6 +161,11 @@ impl Mail {
continue;
}
}
if user.has_role(db, "schnupperant").await {
continue;
}
if !user.has_role(db, "paid").await || test.is_some() {
let mut is_family = false;
let mut send_to = String::new();
@ -273,6 +278,11 @@ Der Vorstand");
continue;
}
}
if user.has_role(db, "schnupperant").await {
continue;
}
if let Some(fee) = user.fee(db).await {
if !fee.paid || test.is_some() {
let mut is_family = false;

View File

@ -519,6 +519,15 @@ impl User {
Ok(())
}
pub(crate) async fn remove_membership_pdf(&self, db: &SqlitePool, updated_by: &ManageUserUser) {
sqlx::query!(
"UPDATE user SET membership_pdf = null where id = ?",
self.id
)
.execute(db)
.await
.unwrap(); //Okay, because we can only create a User of a valid id
}
pub(crate) async fn add_membership_pdf(
&self,
db: &SqlitePool,

View File

@ -1,7 +1,8 @@
use super::User;
use crate::{
BOAT_STORAGE, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND, REGULAR,
RENNRUDERBEITRAG, STUDENT_OR_PUPIL, UNTERSTUETZEND, model::family::Family,
model::family::Family, BOAT_STORAGE, DUAL_MEMBERSHIP, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE,
FAMILY_TWO, FOERDERND, REGULAR, RENNRUDERBEITRAG, STUDENT_OR_PUPIL, TRIAL_ROWING,
TRIAL_ROWING_REDUCED, UNTERSTUETZEND,
};
use chrono::{Datelike, Local, NaiveDate};
use serde::Serialize;
@ -68,6 +69,7 @@ impl User {
if !self.has_role(db, "Donau Linz").await
&& !self.has_role(db, "Unterstützend").await
&& !self.has_role(db, "Förderndes Mitglied").await
&& !self.has_role(db, "schnupperant").await
{
return None;
}
@ -107,6 +109,7 @@ impl User {
if !self.has_role(db, "Donau Linz").await
&& !self.has_role(db, "Unterstützend").await
&& !self.has_role(db, "Förderndes Mitglied").await
&& !self.has_role(db, "schnupperant").await
{
return fee;
}
@ -126,13 +129,16 @@ impl User {
);
}
if let Some(member_since_date) = &self.member_since_date {
if let Ok(member_since_date) = NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d")
{
if member_since_date.year() == Local::now().year()
&& !self.has_role(db, "no-einschreibgebuehr").await
if !self.has_role(db, "schnupperant").await {
if let Some(member_since_date) = &self.member_since_date {
if let Ok(member_since_date) =
NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d")
{
fee.add("Einschreibgebühr".into(), EINSCHREIBGEBUEHR);
if member_since_date.year() == Local::now().year()
&& !self.has_role(db, "no-einschreibgebuehr").await
{
fee.add("Einschreibgebühr".into(), EINSCHREIBGEBUEHR);
}
}
}
}
@ -150,7 +156,13 @@ impl User {
false
};
if self.has_role(db, "Unterstützend").await {
if self.has_role(db, "schnupperant").await {
if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await {
fee.add("Schnupperkurs (reduziert)".into(), TRIAL_ROWING_REDUCED);
} else {
fee.add("Schnupperkurs".into(), TRIAL_ROWING);
}
} else if self.has_role(db, "Unterstützend").await {
fee.add("Unterstützendes Mitglied".into(), UNTERSTUETZEND);
} else if self.has_role(db, "Förderndes Mitglied").await {
fee.add("Förderndes Mitglied".into(), FOERDERND);
@ -163,6 +175,18 @@ impl User {
}
} else if self.has_role(db, "Ehrenmitglied").await {
fee.add("Ehrenmitglied".into(), 0);
} else if self.has_role(db, "dual_membership").await {
if halfprice {
fee.add(
"Doppelmitgliedschaft mit anderem österr. Ruderverein (Halbpreis)".into(),
DUAL_MEMBERSHIP / 2,
);
} else {
fee.add(
"Doppelmitgliedschaft mit anderem österr. Ruderverein".into(),
DUAL_MEMBERSHIP,
);
}
} else if halfprice {
fee.add("Mitgliedsbeitrag (Halbpreis)".into(), REGULAR / 2);
} else {
@ -170,6 +194,19 @@ impl User {
}
}
if !self.has_role(db, "schnupperant").await
&& self.has_role(db, "participated_schnupperkurs").await
{
if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await {
fee.add(
"Anrechnung reduzierter Schnupperkurs".into(),
-TRIAL_ROWING_REDUCED,
);
} else {
fee.add("Anrechnung Schnupperkurs".into(), -TRIAL_ROWING);
}
}
fee
}
}

View File

@ -1,8 +1,7 @@
use super::{ManageUserUser, User};
use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
special_user,
special_user, NonEmptyString,
};
use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt};
@ -93,6 +92,8 @@ Beim nächsten Treffen im Verein, erinnere jemand vom Vorstand (https://rudernli
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.
Falls du deinen Mitgliedsbeitrag noch nicht bezahlt hast, erledige dies bitte demnächst. Den genauen Betrag und einen QR Code, den du mit deiner Bankapp scannen kannst findest du unter https://app.rudernlinz.at/planned
Wir freuen uns darauf, dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln!
Riemen- & Dollenbruch

View File

@ -4,9 +4,9 @@ use super::scheckbuch::ScheckbuchUser;
use super::schnupperinterest::SchnupperInterestUser;
use super::unterstuetzend::UnterstuetzendUser;
use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder;
use crate::model::role::Role;
use crate::NonEmptyString;
use crate::{
model::{mail::Mail, notification::Notification},
special_user,
@ -65,20 +65,32 @@ impl SchnupperantUser {
.await?;
// Change roles
let regular = Role::find_by_name(db, "Donau Linz").await.unwrap();
let paid = Role::find_by_name(db, "paid").await.unwrap();
if self.user.remove_role(db, changed_by, &paid).await.is_err() {
self.remove_membership_pdf(db, changed_by).await;
return Err("Kann noch kein normales Mitglied werden, da die Schnupperkurs-Gebühr noch nicht bezahlt wurde.".into());
}
let scheckbook = Role::find_by_name(db, "schnupperant").await.unwrap();
self.user.remove_role(db, changed_by, &scheckbook).await?;
let regular = Role::find_by_name(db, "Donau Linz").await.unwrap();
self.user.add_role(db, changed_by, &regular).await?;
let participated_schnupperkurs = Role::find_by_name(db, "participated_schnupperkurs")
.await
.unwrap();
self.user
.add_role(db, changed_by, &participated_schnupperkurs)
.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()
"Liebe Steuerberechtigte, {} nahm an unserem Schnupperkurs teil und ist nun seit {member_since} ein neues reguläres Mitglied. 🎉",
self.name
),
"Neues Vereinsmitglied",
None,
@ -203,6 +215,11 @@ impl SchnupperantUser {
.await?;
// Change roles
let paid = Role::find_by_name(db, "paid").await.unwrap();
if self.user.remove_role(db, changed_by, &paid).await.is_err() {
self.remove_membership_pdf(db, changed_by).await;
return Err("Kann noch kein normales Mitglied werden, da die Schnupperkurs-Gebühr noch nicht bezahlt wurde.".into());
}
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?;
@ -267,6 +284,11 @@ impl SchnupperantUser {
.await?;
// Change roles
let paid = Role::find_by_name(db, "paid").await.unwrap();
if self.user.remove_role(db, changed_by, &paid).await.is_err() {
self.remove_membership_pdf(db, changed_by).await;
return Err("Kann noch kein normales Mitglied werden, da die Schnupperkurs-Gebühr noch nicht bezahlt wurde.".into());
}
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?;
@ -307,7 +329,7 @@ impl SchnupperantUser {
// 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.notify_coxes_about_new_schnupperant(db).await;
self.send_welcome_mail_to_user(db, smtp_pw).await?;
ActivityBuilder::new(&format!(
@ -335,19 +357,27 @@ impl SchnupperantUser {
mail,
"ASKÖ Ruderverein Donau Linz | Anmeldung Schnupperkurs",
format!(
"Hallo {0},
"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.
es freut uns sehr, dich bei unserem Schnupperkurs willkommen heißen zu dürfen.
Bitte überweise die {1} auf unser Bankkonto (IBAN: AT58 2032 0321 0072 9256) und gib beim Verwendungszweck 'Schnupperkurs {0}' an.
Detaillierte Informationen folgen noch, du wirst sie ein paar Tage vor dem Termin bekommen (wenn das Wetter/Wasserstand/... abschätzbar ist).
Riemen- & Dollenbruch,
ASKÖ Ruderverein Donau Linz", self.name),
ASKÖ Ruderverein Donau Linz",
self.name,
self.fee(db).await.unwrap().sum_in_cents/100
),
smtp_pw,
).await?;
)
.await?;
Ok(())
}
async fn notify_coxes_about_new_scheckbuch(&self, db: &SqlitePool) {
async fn notify_coxes_about_new_schnupperant(&self, db: &SqlitePool) {
if let Some(role) = Role::find_by_name(db, "schnupper-betreuer").await {
Notification::create_for_role(
db,

View File

@ -5,44 +5,5 @@ INSERT INTO user(name) VALUES('Philipp');
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Philipp'),(SELECT id FROM role where name = 'Donau Linz'));
-- Step 1: Create a new table without the 'notes' column
CREATE TABLE "user_new" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" text NOT NULL UNIQUE,
"pw" text,
"deleted" boolean NOT NULL DEFAULT FALSE,
"last_access" DATETIME,
"dob" text,
"weight" text,
"sex" text,
"dirty_thirty" text,
"dirty_dozen" text,
"member_since_date" text,
"birthdate" text,
"mail" text,
"nickname" text,
"phone" text,
"address" text,
"family_id" INTEGER REFERENCES family(id),
"membership_pdf" BLOB,
"user_token" TEXT NOT NULL DEFAULT (lower(hex(randomblob(16))))
);
-- Step 2: Copy data from the old table to the new one (excluding 'notes')
INSERT INTO user_new (
id, name, pw, deleted, last_access, dob, weight, sex,
dirty_thirty, dirty_dozen, member_since_date, birthdate,
mail, nickname, phone, address, family_id, membership_pdf, user_token
)
SELECT
id, name, pw, deleted, last_access, dob, weight, sex,
dirty_thirty, dirty_dozen, member_since_date, birthdate,
mail, nickname, phone, address, family_id, membership_pdf, user_token
FROM user;
-- Step 3: Drop the old table
DROP TABLE user;
-- Step 4: Rename the new table to the original name
ALTER TABLE user_new RENAME TO user;
insert into role(name, cluster, formatted_name) values('dual_membership', 'financial', 'Doppelmitgliedschaft mit anderem österr. Ruderverein');
insert into role(name, hide_in_lists) values('participated_schnupperkurs', true);