use chrono::NaiveDateTime; use sqlx::PgPool; use tracing::debug; use crate::validation::{ AsyncValidate, AsyncValidateError, start_date_time_lies_before_end_date_time, }; use super::{Assignment, Availability, Event, Function, Role, User}; pub struct AssignmentChangeset { pub function: Function, pub time: (NaiveDateTime, NaiveDateTime), } pub struct AssignmentContext<'a> { pub pool: &'a PgPool, pub user: &'a User, pub event_id: i32, pub availability_id: i32, } impl<'a> AsyncValidate<'a> for AssignmentChangeset { type Context = AssignmentContext<'a>; async fn validate_with_context( &self, context: &'a Self::Context, ) -> Result<(), crate::validation::AsyncValidateError> { let Some(availability) = Availability::read_by_id_including_user(context.pool, context.availability_id).await? else { return Err(AsyncValidateError::new( "Angegebener Verfügbarkeit des Nutzers existiert nicht!", )); }; let Some(event) = Event::read_by_id_including_location(context.pool, context.event_id).await? else { return Err(AsyncValidateError::new( "Angegebenes Event existiert nicht!", )); }; user_is_admin_or_area_manager_of_event_area(context.user, &event)?; availability_user_inside_event_area(&availability, &event)?; available_time_fits(&self.time, &availability)?; start_date_time_lies_before_end_date_time(&self.time.0, &self.time.1)?; availability_not_already_assigned(&self.time, &availability, &event, context.pool).await?; user_of_availability_has_function(&self.function, &availability)?; event_has_free_slot_for_function(&self.function, &availability, &event, context.pool) .await?; Ok(()) } } fn availability_user_inside_event_area( availability: &Availability, event: &Event, ) -> Result<(), AsyncValidateError> { let user = availability.user.as_ref().unwrap(); let location = event.location.as_ref().unwrap(); if user.area_id != location.area_id { return Err(AsyncValidateError::new( "Nutzer der Verfügbarkeit ist nicht im gleichen Bereich wie der Ort der Veranstaltung!", )); } Ok(()) } fn available_time_fits( value: &(NaiveDateTime, NaiveDateTime), availability: &Availability, ) -> Result<(), AsyncValidateError> { if value.0 < availability.start || value.1 > availability.end { return Err(AsyncValidateError::new( "time not made available can't be assigned", )); } Ok(()) } fn user_of_availability_has_function( value: &Function, availability: &Availability, ) -> Result<(), AsyncValidateError> { let user_function = &availability.user.as_ref().unwrap().function; if !user_function.contains(value) { return Err(AsyncValidateError::new( "user has not the required function for this assignment", )); } Ok(()) } async fn event_has_free_slot_for_function( value: &Function, availability: &Availability, event: &Event, pool: &PgPool, ) -> Result<(), AsyncValidateError> { let assignments_for_event: Vec = Assignment::read_all_by_event(pool, event.id) .await? .into_iter() .filter(|a| a.availability_id != availability.id && a.event_id != event.id) .collect(); let assignments_with_function = assignments_for_event .iter() .filter(|a| a.function == *value) .count(); debug!( assignments_with_function, "existing assignments for function" ); if match *value { Function::Posten => assignments_with_function >= event.amount_of_posten as usize, Function::Fuehrungsassistent => { event.voluntary_fuehrungsassistent && assignments_with_function >= 1 } Function::Wachhabender => event.voluntary_wachhabender && assignments_with_function >= 1, } { return Err(AsyncValidateError::new( "event already has enough assignments for this function", )); } Ok(()) } async fn availability_not_already_assigned( time: &(NaiveDateTime, NaiveDateTime), availability: &Availability, event: &Event, pool: &PgPool, ) -> Result<(), AsyncValidateError> { let list: Vec = Assignment::read_all_by_availability(pool, availability.id) .await? .into_iter() .filter(|a| a.availability_id != availability.id && a.event_id != event.id) .collect(); let has_start_time_during_assignment = |a: &Assignment| a.start >= time.0 && a.start <= time.1; let has_end_time_during_assignment = |a: &Assignment| a.end >= time.0 && a.end <= time.1; if list .iter() .any(|a| has_start_time_during_assignment(a) || has_end_time_during_assignment(a)) { return Err(AsyncValidateError::new( "availability is already assigned for that time", )); } Ok(()) } fn user_is_admin_or_area_manager_of_event_area( user: &User, event: &Event, ) -> Result<(), AsyncValidateError> { let user_is_admin = user.role == Role::Admin; let user_is_area_manager_event_area = user.role == Role::AreaManager && user.area_id == event.location.as_ref().unwrap().area_id; if !user_is_admin && !user_is_area_manager_event_area { return Err(AsyncValidateError::new("TODO: admin or areamanager")); } Ok(()) }