use chrono::{Days, NaiveDateTime}; use garde::Validate; use crate::{END_OF_DAY, START_OF_DAY}; use super::{start_date_time_lies_before_end_date_time, Availability}; #[derive(Validate)] #[garde(allow_unvalidated)] #[garde(context(AvailabilityContext))] pub struct AvailabilityChangeset { #[garde( custom(time_is_not_already_made_available), custom(start_date_time_lies_before_end_date_time) )] pub time: (NaiveDateTime, NaiveDateTime), pub comment: Option, } pub struct AvailabilityContext { pub existing_availabilities: Vec, } fn time_is_not_already_made_available( value: &(NaiveDateTime, NaiveDateTime), context: &AvailabilityContext, ) -> garde::Result { if context.existing_availabilities.is_empty() { return Ok(()); } let free_slots = find_free_date_time_slots(&context.existing_availabilities); if free_slots.is_empty() { return Err(garde::Error::new( "cant create a availability as every time slot is already filled", )); } let free_block_found_for_start = free_slots.iter().any(|s| s.0 <= value.0 && s.1 >= value.0); let free_block_found_for_end = free_slots.iter().any(|s| s.0 <= value.1 && s.1 >= value.1); if !free_block_found_for_start || !free_block_found_for_end { return Err(garde::Error::new( "cant create availability as there exists already a availability with the desired time", )); } Ok(()) } pub fn find_free_date_time_slots( availabilities: &[Availability], ) -> Vec<(NaiveDateTime, NaiveDateTime)> { if availabilities.is_empty() { return Vec::new(); } let mut times: Vec<(NaiveDateTime, NaiveDateTime)> = availabilities.iter().map(|a| (a.start, a.end)).collect(); times.sort(); let mut changed = true; while changed { changed = false; for i in 0..(times.len() - 1) { let b = times[i + 1]; let a = times.get_mut(i).unwrap(); if a.1 == b.0 { a.1 = b.1; times.remove(i + 1); changed = true; break; } } } //println!("zeiten unified {times:?}"); let date = times.first().unwrap().0.date(); let date_next_day = date.checked_add_days(Days::new(1)).unwrap(); let start_of_day = date.and_time(START_OF_DAY); let end_of_day = date_next_day.and_time(END_OF_DAY); // now times contains unified list of existing availabilities -> now calculate the "inverse" let mut available_slots = Vec::new(); let start = times.first().unwrap(); if start.0 != start_of_day { available_slots.push((start_of_day, start.0)); } let mut iterator = times.iter().peekable(); while let Some(a) = iterator.next() { if let Some(b) = iterator.peek() { available_slots.push((a.1, b.0)); } } let end = times.last().unwrap(); if end.1 != end_of_day { available_slots.push((end.1, end_of_day)); } available_slots }