181 lines
5.5 KiB
Rust
181 lines
5.5 KiB
Rust
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> = 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> = 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(())
|
|
}
|