use std::collections::HashMap; use crate::model::{boat::Boat, user::User}; 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; #[derive(FromRow, Debug, Serialize, Deserialize)] pub struct BoatReservation { pub id: i64, pub boat_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<i64>, pub created_at: NaiveDateTime, } #[derive(FromRow, Debug, Serialize, Deserialize)] pub struct BoatReservationWithDetails { #[serde(flatten)] reservation: BoatReservation, boat: Boat, user_applicant: User, user_confirmation: Option<User>, } #[derive(Debug)] pub struct BoatReservationToAdd<'r> { pub boat: &'r Boat, pub start_date: NaiveDate, pub end_date: NaiveDate, pub time_desc: &'r str, pub usage: &'r str, pub user_applicant: &'r User, } impl BoatReservation { pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> { sqlx::query_as!( Self, "SELECT id, boat_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at FROM boat_reservation WHERE id like ?", id ) .fetch_one(db) .await .ok() } pub async fn all_future(db: &SqlitePool) -> Vec<BoatReservationWithDetails> { let boatreservations = sqlx::query_as!( Self, " SELECT id, boat_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at FROM boat_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 boatreservations { 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 boat = Boat::find_by_id(db, reservation.boat_id as i32) .await .unwrap(); res.push(BoatReservationWithDetails { reservation, boat, user_applicant, user_confirmation, }); } res } pub async fn all_future_with_groups( db: &SqlitePool, ) -> HashMap<String, Vec<BoatReservationWithDetails>> { let mut grouped_reservations: HashMap<String, Vec<BoatReservationWithDetails>> = 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_insert_with(Vec::new) .push(reservation); } grouped_reservations } pub async fn create( db: &SqlitePool, boatreservation: BoatReservationToAdd<'_>, ) -> Result<(), String> { if Self::boat_reserved_between_dates( db, boatreservation.boat, &boatreservation.start_date, &boatreservation.end_date, ) .await { return Err("Boot in diesem Zeitraum bereits reserviert.".into()); } Log::create(db, format!("New boat reservation: {boatreservation:?}")).await; sqlx::query!( "INSERT INTO boat_reservation(boat_id, start_date, end_date, time_desc, usage, user_id_applicant) VALUES (?,?,?,?,?,?)", boatreservation.boat.id, boatreservation.start_date, boatreservation.end_date, boatreservation.time_desc, boatreservation.usage, boatreservation.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 boatreservation.start_date == boatreservation.end_date { format!("am {}", boatreservation.start_date) } else { format!( "von {} bis {}", boatreservation.start_date, boatreservation.end_date ) }; Notification::create( db, &user, &format!( "{} hat eine neue Bootsreservierung für Boot '{}' {} angelegt. Zeit: {}; Zweck: {}", boatreservation.user_applicant.name, boatreservation.boat.name, date, boatreservation.time_desc, boatreservation.usage ), "Neue Bootsreservierung", None, ) .await; } Ok(()) } pub async fn boat_reserved_between_dates( db: &SqlitePool, boat: &Boat, start_date: &NaiveDate, end_date: &NaiveDate, ) -> bool { sqlx::query!( "SELECT COUNT(*) AS reservation_count FROM boat_reservation WHERE boat_id = ? AND start_date <= ? AND end_date >= ?;", boat.id, end_date, start_date ) .fetch_one(db) .await .unwrap() .reservation_count > 0 } pub async fn delete(&self, db: &SqlitePool) { sqlx::query!("DELETE FROM boat_reservation WHERE id=?", self.id) .execute(db) .await .unwrap(); //Okay, because we can only create a Boat of a valid id } }