use std::collections::HashMap; use chrono::NaiveDate; use chrono::NaiveDateTime; use rocket::serde::{Deserialize, Serialize}; use sqlx::{FromRow, SqlitePool}; use super::log::Log; use super::notification::Notification; use super::role::Role; use super::trailer::Trailer; use super::user::User; use crate::tera::trailerreservation::ReservationEditForm; #[derive(FromRow, Debug, Serialize, Deserialize)] pub struct TrailerReservation { pub id: i64, pub trailer_id: i64, pub start_date: NaiveDate, pub end_date: NaiveDate, pub time_desc: String, pub usage: String, pub user_id_applicant: i64, pub user_id_confirmation: Option, pub created_at: NaiveDateTime, } #[derive(FromRow, Debug, Serialize, Deserialize)] pub struct TrailerReservationWithDetails { #[serde(flatten)] reservation: TrailerReservation, trailer: Trailer, user_applicant: User, user_confirmation: Option, } #[derive(Debug)] pub struct TrailerReservationToAdd<'r> { pub trailer: &'r Trailer, pub start_date: NaiveDate, pub end_date: NaiveDate, pub time_desc: &'r str, pub usage: &'r str, pub user_applicant: &'r User, } impl TrailerReservation { pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { sqlx::query_as!( Self, "SELECT id, trailer_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at FROM trailer_reservation WHERE id like ?", id ) .fetch_one(db) .await .ok() } pub async fn all_future(db: &SqlitePool) -> Vec { let trailerreservations = sqlx::query_as!( Self, " SELECT id, trailer_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at FROM trailer_reservation WHERE end_date >= CURRENT_DATE ORDER BY end_date " ) .fetch_all(db) .await .unwrap(); //TODO: fixme let mut res = Vec::new(); for reservation in trailerreservations { let user_confirmation = match reservation.user_id_confirmation { Some(id) => { let user = User::find_by_id(db, id as i32).await; Some(user.unwrap()) } None => None, }; let user_applicant = User::find_by_id(db, reservation.user_id_applicant as i32) .await .unwrap(); let trailer = Trailer::find_by_id(db, reservation.trailer_id as i32) .await .unwrap(); res.push(TrailerReservationWithDetails { reservation, trailer, user_applicant, user_confirmation, }); } res } pub async fn all_future_with_groups( db: &SqlitePool, ) -> HashMap> { let mut grouped_reservations: HashMap> = HashMap::new(); let reservations = Self::all_future(db).await; for reservation in reservations { let key = format!( "{}-{}-{}-{}-{}", reservation.reservation.start_date, reservation.reservation.end_date, reservation.reservation.time_desc, reservation.reservation.usage, reservation.user_applicant.name ); grouped_reservations .entry(key) .or_default() .push(reservation); } grouped_reservations } pub async fn create( db: &SqlitePool, trailerreservation: TrailerReservationToAdd<'_>, ) -> Result<(), String> { if Self::trailer_reserved_between_dates( db, trailerreservation.trailer, &trailerreservation.start_date, &trailerreservation.end_date, ) .await { return Err("Hänger in diesem Zeitraum bereits reserviert.".into()); } Log::create( db, format!("New trailer reservation: {trailerreservation:?}"), ) .await; sqlx::query!( "INSERT INTO trailer_reservation(trailer_id, start_date, end_date, time_desc, usage, user_id_applicant) VALUES (?,?,?,?,?,?)", trailerreservation.trailer.id, trailerreservation.start_date, trailerreservation.end_date, trailerreservation.time_desc, trailerreservation.usage, trailerreservation.user_applicant.id, ) .execute(db) .await .map_err(|e| e.to_string())?; let board = User::all_with_role(db, &Role::find_by_name(db, "Vorstand").await.unwrap()).await; for user in board { let date = if trailerreservation.start_date == trailerreservation.end_date { format!("am {}", trailerreservation.start_date) } else { format!( "von {} bis {}", trailerreservation.start_date, trailerreservation.end_date ) }; Notification::create( db, &user, &format!( "{} hat eine neue Hängerreservierung für Hänger '{}' {} angelegt. Zeit: {}; Zweck: {}", trailerreservation.user_applicant.name, trailerreservation.trailer.name, date, trailerreservation.time_desc, trailerreservation.usage ), "Neue Hängerreservierung", None,None ) .await; } Ok(()) } pub async fn trailer_reserved_between_dates( db: &SqlitePool, trailer: &Trailer, start_date: &NaiveDate, end_date: &NaiveDate, ) -> bool { sqlx::query!( "SELECT COUNT(*) AS reservation_count FROM trailer_reservation WHERE trailer_id = ? AND start_date <= ? AND end_date >= ?;", trailer.id, end_date, start_date ) .fetch_one(db) .await .unwrap() .reservation_count > 0 } pub async fn update(&self, db: &SqlitePool, data: ReservationEditForm) { let time_desc = data.time_desc.trim(); let usage = data.usage.trim(); sqlx::query!( "UPDATE trailer_reservation SET time_desc = ?, usage = ? where id = ?", time_desc, usage, self.id ) .execute(db) .await .unwrap(); //Okay, because we can only create a User of a valid id } pub async fn delete(&self, db: &SqlitePool) { sqlx::query!("DELETE FROM trailer_reservation WHERE id=?", self.id) .execute(db) .await .unwrap(); //Okay, because we can only create a Boat of a valid id } }