progress @fees, next step, deply+enter families/student/pupil/...
Some checks failed
CI/CD Pipeline / deploy-staging (push) Blocked by required conditions
CI/CD Pipeline / deploy-main (push) Blocked by required conditions
CI/CD Pipeline / test (push) Has been cancelled

This commit is contained in:
2024-01-19 00:39:15 +01:00
parent ff795ce66c
commit 267becfbce
6 changed files with 245 additions and 33 deletions

View File

@ -1,6 +1,8 @@
use serde::Serialize;
use sqlx::{sqlite::SqliteQueryResult, FromRow, SqlitePool};
use super::user::User;
#[derive(FromRow, Serialize, Clone)]
pub struct Family {
id: i64,
@ -46,10 +48,36 @@ GROUP BY family.id;"
.unwrap()
}
pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
sqlx::query_as!(Self, "SELECT id FROM family WHERE id like ?", id)
.fetch_one(db)
.await
.ok()
}
pub async fn find_by_opt_id(db: &SqlitePool, id: Option<i64>) -> Option<Self> {
if let Some(id) = id {
Self::find_by_id(db, id).await
} else {
None
}
}
pub async fn amount_family_members(&self, db: &SqlitePool) -> i32 {
sqlx::query!(
"SELECT COUNT(*) as count FROM user WHERE family_id = ?",
self.id
)
.fetch_one(db)
.await
.unwrap()
.count
}
pub async fn members(&self, db: &SqlitePool) -> Vec<User> {
sqlx::query_as!(User, "SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE family_id = ?", self.id)
.fetch_all(db)
.await
.unwrap()
}
}

View File

@ -16,6 +16,13 @@ use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::{family::Family, log::Log, tripdetails::TripDetails, Day};
use crate::tera::admin::user::UserEditForm;
const RENNRUDERBEITRAG: i32 = 11000;
const BOAT_STORAGE: i32 = 4500;
const FAMILY_TWO: i32 = 30000;
const FAMILY_THREE_OR_MORE: i32 = 35000;
const STUDENT_OR_PUPIL: i32 = 8000;
const REGULAR: i32 = 22000;
#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct User {
pub id: i64,
@ -90,7 +97,112 @@ pub enum LoginError {
DeserializationError,
}
#[derive(Debug, Serialize)]
pub(crate) struct Fee {
pub(crate) sum_in_cents: i32,
pub(crate) parts: Vec<(String, i32)>,
pub(crate) family: bool,
pub(crate) name: String,
}
impl Fee {
pub fn new() -> Self {
Self {
sum_in_cents: 0,
family: false,
name: "".into(),
parts: Vec::new(),
}
}
pub fn family(&mut self) {
self.family = true;
}
pub fn add(&mut self, desc: String, price_in_cents: i32) {
self.sum_in_cents += price_in_cents;
self.parts.push((desc, price_in_cents));
}
pub fn name(&mut self, name: String) {
self.name = name;
}
pub fn merge(&mut self, fee: Fee) {
for (desc, price_in_cents) in fee.parts {
self.add(desc, price_in_cents);
}
}
}
impl User {
pub async fn fee(&self, db: &SqlitePool) -> Option<Fee> {
if !self.has_role(db, "Donau Linz").await {
return None;
}
let mut fee = Fee::new();
if let Some(family) = Family::find_by_opt_id(db, self.family_id).await {
fee.name(format!("{} + Familie", self.name));
fee.family();
for member in family.members(db).await {
fee.merge(member.fee_without_families(db).await);
}
if family.amount_family_members(db).await > 2 {
fee.add("Familie 3+ Personen".into(), FAMILY_THREE_OR_MORE);
} else {
fee.add("Familie 2 Personen".into(), FAMILY_TWO);
}
} else {
fee.name(self.name.clone());
fee.merge(self.fee_without_families(db).await);
}
Some(fee)
}
async fn fee_without_families(&self, db: &SqlitePool) -> Fee {
let mut fee = Fee::new();
if !self.has_role(db, "Donau Linz").await {
return fee;
}
if self.has_role(db, "Rennrudern").await {
fee.add("Rennruderbeitrag".into(), RENNRUDERBEITRAG);
}
let amount_boats = self.amount_boats(db).await;
if amount_boats > 0 {
fee.add(
format!("{}x Bootsplatz", amount_boats),
amount_boats * BOAT_STORAGE,
);
}
if Family::find_by_opt_id(db, self.family_id).await.is_none() {
if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await {
fee.add("Schüler/Student".into(), STUDENT_OR_PUPIL);
} else {
fee.add("Mitgliedsbeitrag".into(), REGULAR);
}
}
fee
}
pub async fn amount_boats(&self, db: &SqlitePool) -> i32 {
sqlx::query!(
"SELECT COUNT(*) as count FROM boat WHERE owner = ?",
self.id
)
.fetch_one(db)
.await
.unwrap()
.count
}
pub async fn rowed_km(&self, db: &SqlitePool) -> i32 {
sqlx::query!(
"SELECT COALESCE(SUM(distance_in_km),0) as rowed_km
@ -640,6 +752,42 @@ impl<'r> FromRequest<'r> for DonauLinzUser {
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VorstandUser(pub(crate) User);
impl Into<User> for VorstandUser {
fn into(self) -> User {
self.0
}
}
impl Deref for VorstandUser {
type Target = User;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[async_trait]
impl<'r> FromRequest<'r> for VorstandUser {
type Error = LoginError;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let db = req.rocket().state::<SqlitePool>().unwrap();
match User::from_request(req).await {
Outcome::Success(user) => {
if user.has_role(db, "Vorstand").await {
Outcome::Success(VorstandUser(user))
} else {
Outcome::Error((Status::Forbidden, LoginError::NotACox))
}
}
Outcome::Error(f) => Outcome::Error(f),
Outcome::Forward(f) => Outcome::Forward(f),
}
}
}
#[cfg(test)]
mod test {
use std::collections::HashMap;