109 lines
3.0 KiB
Rust
109 lines
3.0 KiB
Rust
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<String>,
|
|
}
|
|
|
|
pub struct AvailabilityContext {
|
|
pub existing_availabilities: Vec<Availability>,
|
|
}
|
|
|
|
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
|
|
}
|