feat: finish implementing assignment validation

This commit is contained in:
Max Hohlfeld 2025-07-02 10:14:40 +02:00
parent 512b061c7a
commit bdaf8ff20e
6 changed files with 29 additions and 23 deletions

View File

@ -1,10 +1,9 @@
use anyhow::Context; use anyhow::Context;
use chrono::{Local, NaiveDateTime, Utc}; use chrono::Local;
use sqlx::migrate::Migrate; use sqlx::migrate::Migrate;
use sqlx::{migrate::Migrator, Executor}; use sqlx::{migrate::Migrator, Executor};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::time::SystemTime;
use std::{ use std::{
collections::HashMap, collections::HashMap,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -37,6 +36,7 @@ enum Command {
} }
#[async_std::main] #[async_std::main]
#[allow(unused)]
async fn main() { async fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
let config = load_config(&cli.environment).expect("Could not load config!"); let config = load_config(&cli.environment).expect("Could not load config!");

View File

@ -3,6 +3,7 @@ use sqlx::{PgPool, query};
use super::{AssignmentChangeset, Function, Result}; use super::{AssignmentChangeset, Function, Result};
#[derive(Debug)]
pub struct Assignment { pub struct Assignment {
pub event_id: i32, pub event_id: i32,
pub availability_id: i32, pub availability_id: i32,

View File

@ -31,7 +31,7 @@ impl<'a> AsyncValidate<'a> for AssignmentChangeset {
Availability::read_by_id_including_user(context.pool, context.availability_id).await? Availability::read_by_id_including_user(context.pool, context.availability_id).await?
else { else {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"Angegebener Verfügbarkeit des Nutzers existiert nicht!", "Angegebener Verfügbarkeit des Nutzers existiert nicht.",
)); ));
}; };
@ -39,7 +39,7 @@ impl<'a> AsyncValidate<'a> for AssignmentChangeset {
Event::read_by_id_including_location(context.pool, context.event_id).await? Event::read_by_id_including_location(context.pool, context.event_id).await?
else { else {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"Angegebenes Event existiert nicht!", "Angegebene Veranstaltung existiert nicht.",
)); ));
}; };
@ -65,7 +65,7 @@ fn availability_user_inside_event_area(
if user.area_id != location.area_id { if user.area_id != location.area_id {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"Nutzer der Verfügbarkeit ist nicht im gleichen Bereich wie der Ort der Veranstaltung!", "Nutzer der Verfügbarkeit ist nicht im gleichen Bereich wie der Ort der Veranstaltung.",
)); ));
} }
@ -78,7 +78,7 @@ fn available_time_fits(
) -> Result<(), AsyncValidateError> { ) -> Result<(), AsyncValidateError> {
if value.0 < availability.start || value.1 > availability.end { if value.0 < availability.start || value.1 > availability.end {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"time not made available can't be assigned", "Die verfügbar gemachte Zeit passt nicht zu der zugewiesenen Zeit für die Veranstaltung.",
)); ));
} }
@ -93,7 +93,7 @@ fn user_of_availability_has_function(
if !user_function.contains(value) { if !user_function.contains(value) {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"user has not the required function for this assignment", "Nutzer der Verfügbarkeit besitzt nicht die benötigte Funktion um für diese Position zugewiesen zu werden.",
)); ));
} }
@ -106,12 +106,15 @@ async fn event_has_free_slot_for_function(
event: &Event, event: &Event,
pool: &PgPool, pool: &PgPool,
) -> Result<(), AsyncValidateError> { ) -> Result<(), AsyncValidateError> {
debug!(?event, "event parameter");
let assignments_for_event: Vec<Assignment> = Assignment::read_all_by_event(pool, event.id) let assignments_for_event: Vec<Assignment> = Assignment::read_all_by_event(pool, event.id)
.await? .await?
.into_iter() .into_iter()
.filter(|a| a.availability_id != availability.id && a.event_id != event.id) .filter(|a| a.availability_id != availability.id)
.collect(); .collect();
debug!(?assignments_for_event, "existing assignments for event");
let assignments_with_function = assignments_for_event let assignments_with_function = assignments_for_event
.iter() .iter()
.filter(|a| a.function == *value) .filter(|a| a.function == *value)
@ -119,7 +122,7 @@ async fn event_has_free_slot_for_function(
debug!( debug!(
assignments_with_function, assignments_with_function,
"existing assignments for function" "amount of existing assignments for function"
); );
if match *value { if match *value {
@ -130,7 +133,7 @@ async fn event_has_free_slot_for_function(
Function::Wachhabender => event.voluntary_wachhabender && assignments_with_function >= 1, Function::Wachhabender => event.voluntary_wachhabender && assignments_with_function >= 1,
} { } {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"event already has enough assignments for this function", "Veranstaltung hat bereits genug Zuweisungen für diese Funktion.",
)); ));
} }
@ -146,7 +149,7 @@ async fn availability_not_already_assigned(
let list: Vec<Assignment> = Assignment::read_all_by_availability(pool, availability.id) let list: Vec<Assignment> = Assignment::read_all_by_availability(pool, availability.id)
.await? .await?
.into_iter() .into_iter()
.filter(|a| a.availability_id != availability.id && a.event_id != event.id) .filter(|a| a.event_id != event.id)
.collect(); .collect();
let has_start_time_during_assignment = |a: &Assignment| a.start >= time.0 && a.start <= time.1; let has_start_time_during_assignment = |a: &Assignment| a.start >= time.0 && a.start <= time.1;
@ -157,7 +160,7 @@ async fn availability_not_already_assigned(
.any(|a| has_start_time_during_assignment(a) || has_end_time_during_assignment(a)) .any(|a| has_start_time_during_assignment(a) || has_end_time_during_assignment(a))
{ {
return Err(AsyncValidateError::new( return Err(AsyncValidateError::new(
"availability is already assigned for that time", "Die Verfügbarkeit des Nutzers wurde bereits zu einer anderen Veranstaltung zugewiesen.",
)); ));
} }
@ -173,7 +176,9 @@ fn user_is_admin_or_area_manager_of_event_area(
user.role == Role::AreaManager && user.area_id == event.location.as_ref().unwrap().area_id; user.role == Role::AreaManager && user.area_id == event.location.as_ref().unwrap().area_id;
if !user_is_admin && !user_is_area_manager_event_area { if !user_is_admin && !user_is_area_manager_event_area {
return Err(AsyncValidateError::new("TODO: admin or areamanager")); return Err(AsyncValidateError::new(
"Du verfügst nicht über die Berechtigung, Zuweisungen zu Veranstaltungen vorzunehmen.",
));
} }
Ok(()) Ok(())

View File

@ -85,7 +85,6 @@ mod tests {
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use fake::{Fake, Faker}; use fake::{Fake, Faker};
use sqlx::PgPool; use sqlx::PgPool;
use tracing::info;
use crate::utils::test_helper::{ use crate::utils::test_helper::{
assert_snapshot, test_post, DbTestContext, NaiveDateTimeExt, RequestConfig, assert_snapshot, test_post, DbTestContext, NaiveDateTimeExt, RequestConfig,
@ -248,10 +247,7 @@ mod tests {
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status()); assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, response.status());
} }
#[db_test] // fn fails_when_end_time_lies_before_start_time(context: &DbTestContext) -> not possible as event has the same constraint
fn fails_when_end_time_lies_before_start_time(context: &DbTestContext) {
assert!(false)
}
#[db_test] #[db_test]
fn fails_when_availability_time_already_assigned(context: &DbTestContext) { fn fails_when_availability_time_already_assigned(context: &DbTestContext) {
@ -262,6 +258,7 @@ mod tests {
arrange(&context.db_pool).await; arrange(&context.db_pool).await;
arrange_event(&context.db_pool, start, end, 1).await; arrange_event(&context.db_pool, start, end, 1).await;
arrange_event(&context.db_pool, start, end, 1).await;
arrange_availability(&context.db_pool, start, end).await; arrange_availability(&context.db_pool, start, end).await;
let assignment_changeset = AssignmentChangeset { let assignment_changeset = AssignmentChangeset {
@ -273,7 +270,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=1") let config = RequestConfig::new("/assignments/new?availability=1&function=1&event=2")
.with_role(Role::Admin); .with_role(Role::Admin);
let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await; let response = test_post::<_, _, String>(&context.db_pool, app, &config, None).await;
@ -303,7 +300,6 @@ mod tests {
#[db_test] #[db_test]
fn fails_when_event_already_has_enough_assignments_for_function(context: &DbTestContext) { fn fails_when_event_already_has_enough_assignments_for_function(context: &DbTestContext) {
let app = context.app().await; let app = context.app().await;
info!("Hallo welt");
let start = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 10, 0, 0).unwrap(); let start = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 10, 0, 0).unwrap();
let end = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 20, 0, 0).unwrap(); let end = NaiveDateTime::from_ymd_and_hms(2025, 01, 10, 20, 0, 0).unwrap();

View File

@ -88,6 +88,7 @@ mod tests {
faker::{internet::en::SafeEmail, name::en::Name}, faker::{internet::en::SafeEmail, name::en::Name},
Fake, Faker, Fake, Faker,
}; };
use tracing::debug;
#[db_test] #[db_test]
async fn works_when_user_is_admin(context: &DbTestContext) { async fn works_when_user_is_admin(context: &DbTestContext) {
@ -172,11 +173,14 @@ mod tests {
is_posten: None, is_posten: None,
is_wachhabender: None, is_wachhabender: None,
is_fuehrungsassistent: Some(true), is_fuehrungsassistent: Some(true),
area: Some(2), area: Some(1),
}; };
let response = test_post(&context.db_pool, app, &config, Some(form)).await; let response = test_post(&context.db_pool, app, &config, Some(form)).await;
assert_eq!(StatusCode::FOUND, response.status()); let (status, body) = response.into_status_and_body().await;
debug!(body);
assert_eq!(StatusCode::FOUND, status);
let updated_user = User::read_by_id(&context.db_pool, 1) let updated_user = User::read_by_id(&context.db_pool, 1)
.await .await

View File

@ -41,7 +41,7 @@ mod tests {
use crate::utils::test_helper::{ use crate::utils::test_helper::{
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode, assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode,
}; };
use brass_db::models::{Role, User, Vehicle}; use brass_db::models::{Role, Vehicle};
use brass_macros::db_test; use brass_macros::db_test;
#[db_test] #[db_test]