Compare commits
3 Commits
b42540ac2f
...
38a30e1898
Author | SHA1 | Date | |
---|---|---|---|
38a30e1898 | |||
2ec200831f | |||
d1e067407b |
114
db/.sqlx/query-10b4b80f351b66ac5e778a3031288ac5dc66efd0a66b38b7e30f4c954df91bdf.json
generated
Normal file
114
db/.sqlx/query-10b4b80f351b66ac5e778a3031288ac5dc66efd0a66b38b7e30f4c954df91bdf.json
generated
Normal file
@ -0,0 +1,114 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n event.id AS eventId,\n event.startTimestamp,\n event.endTimestamp,\n event.name,\n event.locationId,\n event.voluntaryWachhabender,\n event.voluntaryFuehrungsassistent,\n event.amountOfPosten,\n event.clothing,\n event.canceled,\n event.note,\n location.id,\n location.name AS locationName,\n location.areaId AS locationAreaId,\n clothing.id AS clothingId,\n clothing.name AS clothingName\n FROM event\n JOIN location ON event.locationId = location.id\n JOIN clothing ON event.clothing = clothing.id\n WHERE starttimestamp::date >= $1\n AND starttimestamp::date <= $2\n AND location.areaId = $3;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "eventid",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "starttimestamp",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "endtimestamp",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "locationid",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "voluntarywachhabender",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "voluntaryfuehrungsassistent",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "amountofposten",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "clothing",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "canceled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "locationname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "locationareaid",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "clothingid",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "clothingname",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Date",
|
||||
"Date",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "10b4b80f351b66ac5e778a3031288ac5dc66efd0a66b38b7e30f4c954df91bdf"
|
||||
}
|
48
db/.sqlx/query-f60053118df6a791d31fa258ee3737881f8f97ca41cbebd92eb22c967292d2ee.json
generated
Normal file
48
db/.sqlx/query-f60053118df6a791d31fa258ee3737881f8f97ca41cbebd92eb22c967292d2ee.json
generated
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n availability.id,\n availability.userId,\n availability.startTimestamp,\n availability.endTimestamp,\n availability.comment\n FROM availability\n WHERE availability.userId = $1\n AND availability.starttimestamp::date >= $2\n AND availability.endtimestamp::date <= $3;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "userid",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "starttimestamp",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "endtimestamp",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "comment",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Date",
|
||||
"Date"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "f60053118df6a791d31fa258ee3737881f8f97ca41cbebd92eb22c967292d2ee"
|
||||
}
|
@ -341,6 +341,46 @@ impl Availability {
|
||||
Ok(availabilities)
|
||||
}
|
||||
|
||||
pub async fn read_by_user_and_daterange(
|
||||
pool: &PgPool,
|
||||
user_id: i32,
|
||||
date_range: (&NaiveDate, &NaiveDate),
|
||||
) -> Result<Vec<Availability>> {
|
||||
let records = query!(
|
||||
r##"
|
||||
SELECT
|
||||
availability.id,
|
||||
availability.userId,
|
||||
availability.startTimestamp,
|
||||
availability.endTimestamp,
|
||||
availability.comment
|
||||
FROM availability
|
||||
WHERE availability.userId = $1
|
||||
AND availability.starttimestamp::date >= $2
|
||||
AND availability.endtimestamp::date <= $3;
|
||||
"##,
|
||||
user_id,
|
||||
date_range.0,
|
||||
date_range.1
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let availabilities = records
|
||||
.iter()
|
||||
.map(|r| Availability {
|
||||
id: r.id,
|
||||
user_id: r.userid,
|
||||
user: None,
|
||||
start: r.starttimestamp.naive_utc(),
|
||||
end: r.endtimestamp.naive_utc(),
|
||||
comment: r.comment.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(availabilities)
|
||||
}
|
||||
|
||||
pub async fn find_adjacent_by_time_for_user(
|
||||
pool: &PgPool,
|
||||
start: &NaiveDateTime,
|
||||
|
@ -2,9 +2,11 @@ use chrono::{Days, NaiveDateTime};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::Availability;
|
||||
use crate::{validation::{
|
||||
start_date_time_lies_before_end_date_time, AsyncValidate, AsyncValidateError
|
||||
}, END_OF_DAY, START_OF_DAY};
|
||||
use crate::{
|
||||
END_OF_DAY, START_OF_DAY,
|
||||
models::Assignment,
|
||||
validation::{AsyncValidate, AsyncValidateError, start_date_time_lies_before_end_date_time},
|
||||
};
|
||||
|
||||
pub struct AvailabilityChangeset {
|
||||
pub time: (NaiveDateTime, NaiveDateTime),
|
||||
@ -14,7 +16,7 @@ pub struct AvailabilityChangeset {
|
||||
pub struct AvailabilityContext<'a> {
|
||||
pub pool: &'a PgPool,
|
||||
pub user_id: i32,
|
||||
pub availability_to_get_edited: Option<i32>,
|
||||
pub availability: Option<i32>,
|
||||
}
|
||||
|
||||
impl<'a> AsyncValidate<'a> for AvailabilityChangeset {
|
||||
@ -28,48 +30,69 @@ impl<'a> AsyncValidate<'a> for AvailabilityChangeset {
|
||||
Availability::read_by_user_and_date(context.pool, context.user_id, &self.time.0.date())
|
||||
.await?;
|
||||
|
||||
if let Some(existing) = context.availability_to_get_edited {
|
||||
start_date_time_lies_before_end_date_time(&self.time.0, &self.time.1)?;
|
||||
|
||||
if let Some(availability) = context.availability {
|
||||
existing_availabilities = existing_availabilities
|
||||
.into_iter()
|
||||
.filter(|a| a.id != existing)
|
||||
.filter(|a| a.id != availability)
|
||||
.collect();
|
||||
|
||||
time_is_not_already_assigned(&self.time, availability, context.pool).await?;
|
||||
}
|
||||
|
||||
time_is_not_already_made_available(&self.time, &existing_availabilities)?;
|
||||
start_date_time_lies_before_end_date_time(&self.time.0, &self.time.1)?;
|
||||
if !existing_availabilities.is_empty() {
|
||||
time_is_not_already_made_available(&self.time, &existing_availabilities)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn time_is_not_already_made_available(
|
||||
value: &(NaiveDateTime, NaiveDateTime),
|
||||
(start, end): &(NaiveDateTime, NaiveDateTime),
|
||||
existing_availabilities: &Vec<Availability>,
|
||||
) -> Result<(), AsyncValidateError> {
|
||||
if existing_availabilities.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let free_slots = find_free_date_time_slots(existing_availabilities);
|
||||
|
||||
if free_slots.is_empty() {
|
||||
return Err(AsyncValidateError::new(
|
||||
"cant create a availability as every time slot is already filled",
|
||||
"Verfügbarkeit kann nicht erstellt werden, da bereits alle Zeiträume verfügbar gemacht wurden.",
|
||||
));
|
||||
}
|
||||
|
||||
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);
|
||||
let free_block_found_for_start = free_slots.iter().any(|s| s.0 <= *start && s.1 >= *start);
|
||||
let free_block_found_for_end = free_slots.iter().any(|s| s.0 <= *end && s.1 >= *end);
|
||||
let is_already_present_as_is = existing_availabilities
|
||||
.iter()
|
||||
.any(|a| a.start == *start && a.end == a.end);
|
||||
|
||||
if !free_block_found_for_start || !free_block_found_for_end {
|
||||
if !free_block_found_for_start || !free_block_found_for_end || is_already_present_as_is {
|
||||
return Err(AsyncValidateError::new(
|
||||
"cant create availability as there exists already a availability with the desired time",
|
||||
"Verfügbarkeit kann nicht erstellt werden, da eine vorhandene Verfügbarkeit überschnitten würde.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn time_is_not_already_assigned(
|
||||
(start, end): &(NaiveDateTime, NaiveDateTime),
|
||||
availability: i32,
|
||||
pool: &PgPool,
|
||||
) -> Result<(), AsyncValidateError> {
|
||||
let existing_assignments = Assignment::read_all_by_availability(pool, availability).await?;
|
||||
for a in existing_assignments {
|
||||
if a.start < *start || a.end > *end {
|
||||
return Err(AsyncValidateError::new(
|
||||
"Verfügbarkeitszeit kann nicht verkleinert werden, da bereits eine Planung für diese Zeit existiert.",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn find_free_date_time_slots(
|
||||
availabilities: &[Availability],
|
||||
) -> Vec<(NaiveDateTime, NaiveDateTime)> {
|
||||
|
@ -95,6 +95,73 @@ impl Event {
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
pub async fn read_all_by_daterange_and_area_including_location(
|
||||
pool: &PgPool,
|
||||
date_range: (&NaiveDate, &NaiveDate),
|
||||
area_id: i32,
|
||||
) -> Result<Vec<Event>> {
|
||||
let records = query!(
|
||||
r#"
|
||||
SELECT
|
||||
event.id AS eventId,
|
||||
event.startTimestamp,
|
||||
event.endTimestamp,
|
||||
event.name,
|
||||
event.locationId,
|
||||
event.voluntaryWachhabender,
|
||||
event.voluntaryFuehrungsassistent,
|
||||
event.amountOfPosten,
|
||||
event.clothing,
|
||||
event.canceled,
|
||||
event.note,
|
||||
location.id,
|
||||
location.name AS locationName,
|
||||
location.areaId AS locationAreaId,
|
||||
clothing.id AS clothingId,
|
||||
clothing.name AS clothingName
|
||||
FROM event
|
||||
JOIN location ON event.locationId = location.id
|
||||
JOIN clothing ON event.clothing = clothing.id
|
||||
WHERE starttimestamp::date >= $1
|
||||
AND starttimestamp::date <= $2
|
||||
AND location.areaId = $3;
|
||||
"#,
|
||||
date_range.0,
|
||||
date_range.1,
|
||||
area_id
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let events = records
|
||||
.into_iter()
|
||||
.map(|record| Event {
|
||||
id: record.eventid,
|
||||
start: record.starttimestamp.naive_utc(),
|
||||
end: record.endtimestamp.naive_utc(),
|
||||
name: record.name.to_string(),
|
||||
location_id: record.locationid,
|
||||
location: Some(Location {
|
||||
id: record.locationid,
|
||||
name: record.locationname.to_string(),
|
||||
area_id: record.locationareaid,
|
||||
area: None,
|
||||
}),
|
||||
voluntary_wachhabender: record.voluntarywachhabender,
|
||||
voluntary_fuehrungsassistent: record.voluntaryfuehrungsassistent,
|
||||
amount_of_posten: record.amountofposten,
|
||||
clothing: Clothing {
|
||||
id: record.clothingid,
|
||||
name: record.clothingname,
|
||||
},
|
||||
canceled: record.canceled,
|
||||
note: record.note,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
pub async fn read_by_id_including_location(pool: &PgPool, id: i32) -> Result<Option<Event>> {
|
||||
let record = query!(
|
||||
r#"
|
||||
|
@ -5,8 +5,8 @@ mod validation_trait;
|
||||
use chrono::NaiveDateTime;
|
||||
pub use email::email_is_valid;
|
||||
pub use error::AsyncValidateError;
|
||||
pub use validation_trait::AsyncValidate;
|
||||
use sqlx::PgPool;
|
||||
pub use validation_trait::AsyncValidate;
|
||||
|
||||
pub struct DbContext<'a> {
|
||||
pub pool: &'a PgPool,
|
||||
@ -24,7 +24,7 @@ pub fn start_date_time_lies_before_end_date_time(
|
||||
) -> Result<(), AsyncValidateError> {
|
||||
if start >= end {
|
||||
return Err(AsyncValidateError::new(
|
||||
"endtime can't lie before starttime",
|
||||
"Ende kann nicht vor dem Beginn liegen.",
|
||||
));
|
||||
}
|
||||
|
||||
|
158
web/src/endpoints/availability/get_calendar.rs
Normal file
158
web/src/endpoints/availability/get_calendar.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
use chrono::{NaiveDate, Utc};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
filters,
|
||||
utils::{
|
||||
event_planning_template::generate_vehicles_assigned_and_available,
|
||||
ApplicationError,
|
||||
DateTimeFormat::{DayMonthYearHourMinute, HourMinute, WeekdayDayMonthYear},
|
||||
TemplateResponse,
|
||||
},
|
||||
};
|
||||
use brass_db::models::{
|
||||
find_free_date_time_slots, Area, Assignment, Availability, Event, Function, Role, User, Vehicle,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CalendarQuery {
|
||||
date: Option<NaiveDate>,
|
||||
area: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "calendar.html")]
|
||||
struct CalendarTemplate {
|
||||
user: User,
|
||||
user_can_create_availability: bool,
|
||||
date: NaiveDate,
|
||||
selected_area: Option<i32>,
|
||||
areas: Vec<Area>,
|
||||
events_and_assignments: Vec<(
|
||||
Event,
|
||||
Vec<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Vec<Vehicle>,
|
||||
)>,
|
||||
availabilities: Vec<Availability>,
|
||||
}
|
||||
|
||||
#[actix_web::get("/calendar")]
|
||||
async fn get(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<CalendarQuery>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
let date = match query.date {
|
||||
Some(given_date) => given_date,
|
||||
None => Utc::now().date_naive(),
|
||||
};
|
||||
|
||||
let areas = Area::read_all(pool.get_ref()).await?;
|
||||
|
||||
let selected_area = match query.area {
|
||||
Some(id) => {
|
||||
if !areas.iter().any(|a| a.id == id) {
|
||||
return Ok(HttpResponse::BadRequest().finish());
|
||||
}
|
||||
|
||||
Some(id)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let availabilities = Availability::read_by_date_and_area_including_user(
|
||||
pool.get_ref(),
|
||||
date,
|
||||
query.area.unwrap_or(user.area_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let availabilities_from_user =
|
||||
Availability::read_by_user_and_date(pool.get_ref(), user.id, &date).await?;
|
||||
//println!("{availabilities_from_user:#?}");
|
||||
|
||||
let user_can_create_availability = availabilities_from_user.is_empty()
|
||||
|| !find_free_date_time_slots(&availabilities_from_user).is_empty();
|
||||
|
||||
//println!("{} || {} || {} = {user_can_create_availability}", availabilities_from_user.is_empty(),
|
||||
// !only_one_availability_exists_and_is_whole_day(&availabilities_from_user),
|
||||
// !find_free_time_slots(&availabilities_from_user).is_empty());
|
||||
|
||||
let mut events_and_assignments = Vec::new();
|
||||
for e in Event::read_all_by_date_and_area_including_location(
|
||||
pool.get_ref(),
|
||||
date,
|
||||
query.area.unwrap_or(user.area_id),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
{
|
||||
let assignments = Assignment::read_all_by_event(pool.get_ref(), e.id).await?;
|
||||
let (posten, rest): (Vec<Assignment>, Vec<Assignment>) = assignments
|
||||
.into_iter()
|
||||
.partition(|a| a.function == Function::Posten);
|
||||
let (wachhabender, fuehrungsassistent): (Vec<Assignment>, Vec<Assignment>) = rest
|
||||
.into_iter()
|
||||
.partition(|a| a.function == Function::Wachhabender);
|
||||
|
||||
let (assigned_vehicle, _) = generate_vehicles_assigned_and_available(&pool, &e).await?;
|
||||
|
||||
events_and_assignments.push((
|
||||
e,
|
||||
posten
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
availabilities
|
||||
.iter()
|
||||
.find(|a| a.id == p.availability_id)
|
||||
.unwrap()
|
||||
.user
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.name
|
||||
.clone()
|
||||
})
|
||||
.collect(),
|
||||
fuehrungsassistent.first().map(|fa| {
|
||||
availabilities
|
||||
.iter()
|
||||
.find(|a| a.id == fa.availability_id)
|
||||
.unwrap()
|
||||
.user
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.name
|
||||
.clone()
|
||||
}),
|
||||
wachhabender.first().map(|wh| {
|
||||
availabilities
|
||||
.iter()
|
||||
.find(|a| a.id == wh.availability_id)
|
||||
.unwrap()
|
||||
.user
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.name
|
||||
.clone()
|
||||
}),
|
||||
assigned_vehicle,
|
||||
));
|
||||
}
|
||||
|
||||
let template = CalendarTemplate {
|
||||
user: user.into_inner(),
|
||||
user_can_create_availability,
|
||||
date,
|
||||
selected_area,
|
||||
areas,
|
||||
events_and_assignments,
|
||||
availabilities,
|
||||
};
|
||||
|
||||
Ok(template.to_response()?)
|
||||
}
|
@ -1,43 +1,23 @@
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use actix_web::{web, Responder};
|
||||
use askama::Template;
|
||||
use chrono::{NaiveDate, Utc};
|
||||
use serde::Deserialize;
|
||||
use chrono::{Months, Utc};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
filters,
|
||||
utils::{
|
||||
event_planning_template::generate_vehicles_assigned_and_available,
|
||||
ApplicationError,
|
||||
DateTimeFormat::{DayMonthYearHourMinute, HourMinute, WeekdayDayMonthYear},
|
||||
TemplateResponse,
|
||||
},
|
||||
};
|
||||
use brass_db::models::{
|
||||
find_free_date_time_slots, Area, Assignment, Availability, Event, Function, Role, User, Vehicle,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CalendarQuery {
|
||||
date: Option<NaiveDate>,
|
||||
area: Option<i32>,
|
||||
}
|
||||
use brass_db::models::{Assignment, Availability, Event, Function, Role, User};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct CalendarTemplate {
|
||||
#[template(path = "overview.html")]
|
||||
struct OverviewTemplate {
|
||||
user: User,
|
||||
user_can_create_availability: bool,
|
||||
date: NaiveDate,
|
||||
selected_area: Option<i32>,
|
||||
areas: Vec<Area>,
|
||||
events_and_assignments: Vec<(
|
||||
Event,
|
||||
Vec<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Vec<Vehicle>,
|
||||
)>,
|
||||
events: Vec<Event>,
|
||||
availabilities: Vec<Availability>,
|
||||
}
|
||||
|
||||
@ -45,112 +25,24 @@ struct CalendarTemplate {
|
||||
async fn get(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<CalendarQuery>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
let date = match query.date {
|
||||
Some(given_date) => given_date,
|
||||
None => Utc::now().date_naive(),
|
||||
};
|
||||
let today = Utc::now().date_naive();
|
||||
let next_month = today.checked_add_months(Months::new(1)).unwrap();
|
||||
|
||||
let areas = Area::read_all(pool.get_ref()).await?;
|
||||
|
||||
let selected_area = match query.area {
|
||||
Some(id) => {
|
||||
if !areas.iter().any(|a| a.id == id) {
|
||||
return Ok(HttpResponse::BadRequest().finish());
|
||||
}
|
||||
|
||||
Some(id)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let availabilities = Availability::read_by_date_and_area_including_user(
|
||||
let events = Event::read_all_by_daterange_and_area_including_location(
|
||||
pool.get_ref(),
|
||||
date,
|
||||
query.area.unwrap_or(user.area_id),
|
||||
(&today, &next_month),
|
||||
user.area_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let availabilities_from_user =
|
||||
Availability::read_by_user_and_date(pool.get_ref(), user.id, &date).await?;
|
||||
//println!("{availabilities_from_user:#?}");
|
||||
let availabilities =
|
||||
Availability::read_by_user_and_daterange(pool.get_ref(), user.id, (&today, &next_month))
|
||||
.await?;
|
||||
|
||||
let user_can_create_availability = availabilities_from_user.is_empty()
|
||||
|| !find_free_date_time_slots(&availabilities_from_user).is_empty();
|
||||
|
||||
//println!("{} || {} || {} = {user_can_create_availability}", availabilities_from_user.is_empty(),
|
||||
// !only_one_availability_exists_and_is_whole_day(&availabilities_from_user),
|
||||
// !find_free_time_slots(&availabilities_from_user).is_empty());
|
||||
|
||||
let mut events_and_assignments = Vec::new();
|
||||
for e in Event::read_all_by_date_and_area_including_location(
|
||||
pool.get_ref(),
|
||||
date,
|
||||
query.area.unwrap_or(user.area_id),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
{
|
||||
let assignments = Assignment::read_all_by_event(pool.get_ref(), e.id).await?;
|
||||
let (posten, rest): (Vec<Assignment>, Vec<Assignment>) = assignments
|
||||
.into_iter()
|
||||
.partition(|a| a.function == Function::Posten);
|
||||
let (wachhabender, fuehrungsassistent): (Vec<Assignment>, Vec<Assignment>) = rest
|
||||
.into_iter()
|
||||
.partition(|a| a.function == Function::Wachhabender);
|
||||
|
||||
let (assigned_vehicle, _) = generate_vehicles_assigned_and_available(&pool, &e).await?;
|
||||
|
||||
events_and_assignments.push((
|
||||
e,
|
||||
posten
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
availabilities
|
||||
.iter()
|
||||
.find(|a| a.id == p.availability_id)
|
||||
.unwrap()
|
||||
.user
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.name
|
||||
.clone()
|
||||
})
|
||||
.collect(),
|
||||
fuehrungsassistent.first().map(|fa| {
|
||||
availabilities
|
||||
.iter()
|
||||
.find(|a| a.id == fa.availability_id)
|
||||
.unwrap()
|
||||
.user
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.name
|
||||
.clone()
|
||||
}),
|
||||
wachhabender.first().map(|wh| {
|
||||
availabilities
|
||||
.iter()
|
||||
.find(|a| a.id == wh.availability_id)
|
||||
.unwrap()
|
||||
.user
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.name
|
||||
.clone()
|
||||
}),
|
||||
assigned_vehicle,
|
||||
));
|
||||
}
|
||||
|
||||
let template = CalendarTemplate {
|
||||
let template = OverviewTemplate {
|
||||
user: user.into_inner(),
|
||||
user_can_create_availability,
|
||||
date,
|
||||
selected_area,
|
||||
areas,
|
||||
events_and_assignments,
|
||||
events,
|
||||
availabilities,
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
use brass_db::models::{Role, User};
|
||||
|
||||
pub mod delete;
|
||||
pub mod get_calendar;
|
||||
pub mod get_new;
|
||||
pub mod get_overview;
|
||||
pub mod get_update;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||
use maud::html;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
@ -22,7 +23,7 @@ pub async fn post(
|
||||
let context = AvailabilityContext {
|
||||
pool: pool.get_ref(),
|
||||
user_id: user.id,
|
||||
availability_to_get_edited: None,
|
||||
availability: None,
|
||||
};
|
||||
|
||||
let mut changeset = AvailabilityChangeset {
|
||||
@ -31,7 +32,13 @@ pub async fn post(
|
||||
};
|
||||
|
||||
if let Err(e) = changeset.validate_with_context(&context).await {
|
||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
||||
let error_message = html! {
|
||||
svg class="icon is-small" {
|
||||
use href="/static/feather-sprite.svg#alert-triangle" {}
|
||||
}
|
||||
" " (e)
|
||||
};
|
||||
return Ok(HttpResponse::UnprocessableEntity().body(error_message.into_string()));
|
||||
};
|
||||
|
||||
if let Some(a) =
|
||||
|
@ -1,4 +1,5 @@
|
||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||
use maud::html;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
@ -31,7 +32,7 @@ pub async fn post(
|
||||
let context = AvailabilityContext {
|
||||
pool: pool.get_ref(),
|
||||
user_id: user.id,
|
||||
availability_to_get_edited: Some(availability.id),
|
||||
availability: Some(availability.id),
|
||||
};
|
||||
|
||||
let mut changeset = AvailabilityChangeset {
|
||||
@ -40,7 +41,13 @@ pub async fn post(
|
||||
};
|
||||
|
||||
if let Err(e) = changeset.validate_with_context(&context).await {
|
||||
return Ok(HttpResponse::BadRequest().body(e.to_string()));
|
||||
let error_message = html! {
|
||||
svg class="icon is-small" {
|
||||
use href="/static/feather-sprite.svg#alert-triangle" {}
|
||||
}
|
||||
" " (e)
|
||||
};
|
||||
return Ok(HttpResponse::UnprocessableEntity().body(error_message.into_string()));
|
||||
};
|
||||
|
||||
if let Some(a) = Availability::find_adjacent_by_time_for_user(
|
||||
|
@ -56,10 +56,11 @@ pub fn init(cfg: &mut ServiceConfig) {
|
||||
|
||||
cfg.service(availability::delete::delete);
|
||||
cfg.service(availability::get_new::get);
|
||||
cfg.service(availability::get_overview::get);
|
||||
cfg.service(availability::get_calendar::get);
|
||||
cfg.service(availability::get_update::get);
|
||||
cfg.service(availability::post_new::post);
|
||||
cfg.service(availability::post_update::post);
|
||||
cfg.service(availability::get_overview::get);
|
||||
|
||||
cfg.service(events::put_cancelation::put_cancel);
|
||||
cfg.service(events::put_cancelation::put_uncancel);
|
||||
|
@ -4,7 +4,8 @@
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{% let is_edit = id.is_some() %}
|
||||
<form method="post" action="/availability/{% if is_edit %}edit/{{ id.unwrap() }}{% else %}new{% endif %}">
|
||||
<form hx-post="/availability/{% if is_edit %}edit/{{ id.unwrap() }}{% else %}new{% endif %}" hx-target="body"
|
||||
hx-target-422="#error">
|
||||
<h1 class="title">{% if is_edit %}Bearbeite{% else %}Neue{% endif %} Vefügbarkeit für {{
|
||||
date|fmt_date(WeekdayDayMonthYear) }}</h1>
|
||||
|
||||
@ -18,7 +19,7 @@
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<input class="input" type="time" name="starttime" required {{ start|insert_time_value|safe }}
|
||||
_="on change put the value of me into #st">
|
||||
_="on change put the value of me into #st then put '' into #error">
|
||||
{% if slot_suggestions.len() > 0 %}
|
||||
<p class="help">noch mögliche Zeiträume:</p>
|
||||
<div class="tags help">
|
||||
@ -32,7 +33,7 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<input class="input" type="time" name="endtime" required {{ end|insert_time_value|safe }}
|
||||
_='on change put the value of me into #et
|
||||
_='on change put the value of me into #et then put "" into #error
|
||||
then if (value of the previous <input/>) is greater than (value of me)
|
||||
then set the value of #enddate to "{{ datetomorrow }}"
|
||||
then put "{{ datetomorrow|fmt_date(WeekdayDayMonth) }}" into #ed
|
||||
@ -53,13 +54,15 @@
|
||||
<label class="radio">
|
||||
<input type="radio" name="isovernight" {{ is_overnight|invert|ref|cond_show("checked")|safe }}
|
||||
_='on click set the value of #enddate to "{{ date }}"
|
||||
then put "{{ date|fmt_date(WeekdayDayMonth) }}" into #ed'>
|
||||
then put "{{ date|fmt_date(WeekdayDayMonth) }}" into #ed
|
||||
then put "" into #error'>
|
||||
am selben Tag
|
||||
</label>
|
||||
<label class="radio ml-3">
|
||||
<input type="radio" id="radionextday" name="isovernight" {{ is_overnight|cond_show("checked")|safe }}
|
||||
_='on click set the value of #enddate to "{{ datetomorrow }}"
|
||||
then put "{{ datetomorrow|fmt_date(WeekdayDayMonth) }}" into #ed'>
|
||||
then put "{{ datetomorrow|fmt_date(WeekdayDayMonth) }}" into #ed
|
||||
then put "" into #error'>
|
||||
am Tag darauf
|
||||
</label>
|
||||
</div>
|
||||
@ -90,6 +93,7 @@
|
||||
<span id="et">{{ end_time|fmt_time(HourMinute) }}</span>
|
||||
Uhr
|
||||
</p>
|
||||
<p id="error" class="help is-danger"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -99,7 +103,7 @@
|
||||
<div class="field-body">
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-success">
|
||||
<button class="button is-success" _="on click put '' into #error">
|
||||
<svg class="icon">
|
||||
<use href="/static/feather-sprite.svg#check-circle" />
|
||||
</svg>
|
||||
|
@ -18,6 +18,10 @@
|
||||
<div class="navbar-menu" id="navMenu">
|
||||
<div hx-boost="true" class="navbar-start">
|
||||
<a href="/" class="navbar-item">
|
||||
Übersicht
|
||||
</a>
|
||||
|
||||
<a href="/calendar" class="navbar-item">
|
||||
Kalender
|
||||
</a>
|
||||
|
||||
|
9
web/templates/overview.html
Normal file
9
web/templates/overview.html
Normal file
@ -0,0 +1,9 @@
|
||||
{% extends "nav.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section id="progress" class="section">
|
||||
<div class="container">
|
||||
Übersicht
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
@ -3,7 +3,8 @@
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<form hx-post="/users/{% if let Some(id) = id %}edit/{{ id }}{% else %}new{% endif %}" hx-target-422="find p">
|
||||
<form hx-post="/users/{% if let Some(id) = id %}edit/{{ id }}{% else %}new{% endif %}" hx-target="body"
|
||||
hx-target-422="find p">
|
||||
<h1 class="title">
|
||||
{% if let Some(name) = name %}Nutzer '{{ name }}' bearbeiten{% else %}Neuen Nutzer anlegen{% endif %}
|
||||
</h1>
|
||||
|
Loading…
x
Reference in New Issue
Block a user