brass/web/src/models/availability_changeset.rs

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
}