diff --git a/db/sql/event/read_all_by_date_range_and_assigned_user_in_other_area.sql b/db/sql/event/read_all_by_date_range_and_assigned_user_in_other_area.sql new file mode 100644 index 00000000..c3cb7255 --- /dev/null +++ b/db/sql/event/read_all_by_date_range_and_assigned_user_in_other_area.sql @@ -0,0 +1,34 @@ +SELECT + event.id AS eventId, + event.startTimestamp, + event.endTimestamp, + event.name, + event.locationId, + event.voluntaryWachhabender, + event.fuehrungsassistentRequired, + 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 + assignment +JOIN availability ON + assignment.availabilityid = availability.id +JOIN event ON + assignment.eventid = "event".id +JOIN location ON + event.locationid = location.id +JOIN clothing ON + event.clothing = clothing.id +WHERE + userid = $1 + AND event.starttimestamp::date >= $2 + AND event.starttimestamp::date <= $3 + AND location.areaId != $4 +ORDER BY + event.starttimestamp; diff --git a/db/sql/event_for_calendar/read_all_by_date_and_area_and_user.sql b/db/sql/event_for_calendar/read_all_by_date_and_area_and_user.sql new file mode 100644 index 00000000..b82c6b0e --- /dev/null +++ b/db/sql/event_for_calendar/read_all_by_date_and_area_and_user.sql @@ -0,0 +1,57 @@ +SELECT DISTINCT + event.id AS eventId, + event.startTimestamp, + event.endTimestamp, + event.name, + event.locationId, + event.voluntaryWachhabender, + event.fuehrungsassistentRequired, + event.amountOfPosten, + event.clothing, + event.canceled, + event.note, + location.id, + location.name AS locationName, + location.areaId AS locationAreaId, + area.name AS areaName, + clothing.id AS clothingId, + clothing.name AS clothingName, + ARRAY ( + SELECT + ROW (user_.name, + assignment.function) ::simpleAssignment + FROM + ASSIGNMENT + JOIN availability ON + assignment.availabilityid = availability.id + JOIN user_ ON + availability.userid = user_.id + WHERE + assignment.eventId = event.id) AS "assignments: Vec", + ARRAY ( + SELECT + vehicle.radiocallname || ' - ' || vehicle.station + FROM + vehicleassignment + JOIN vehicle ON + vehicleassignment.vehicleId = vehicle.id + WHERE + vehicleassignment.eventId = event.id) AS vehicles +FROM + EVENT +JOIN LOCATION ON + event.locationId = location.id +JOIN AREA ON + location.areaId = area.id +JOIN clothing ON + event.clothing = clothing.id +LEFT JOIN ASSIGNMENT ON + event.id = assignment.eventid +LEFT JOIN availability ON + assignment.availabilityid = availability.id +WHERE + event.starttimestamp::date = $1 + AND ( + location.areaId = $2 + OR availability.userId = $3 + ) diff --git a/db/src/models/assignment_changeset.rs b/db/src/models/assignment_changeset.rs index 3fbb4747..4f6a79e3 100644 --- a/db/src/models/assignment_changeset.rs +++ b/db/src/models/assignment_changeset.rs @@ -63,7 +63,7 @@ fn availability_user_inside_event_area( let user = availability.user.as_ref().unwrap(); let location = event.location.as_ref().unwrap(); - if user.area_id != location.area_id { + if user.area_id != location.area_id && !availability.cross_areal { return Err(AsyncValidateError::new( "Nutzer der Verfügbarkeit ist nicht im gleichen Bereich wie der Ort der Veranstaltung.", )); diff --git a/db/src/models/availability_changeset.rs b/db/src/models/availability_changeset.rs index 0e8b89ac..ab5d4f15 100644 --- a/db/src/models/availability_changeset.rs +++ b/db/src/models/availability_changeset.rs @@ -1,4 +1,4 @@ -use chrono::{Days, NaiveDate, NaiveDateTime}; +use chrono::{Days, NaiveDateTime}; use sqlx::PgPool; use super::Availability; @@ -154,7 +154,7 @@ pub fn find_free_date_time_slots( #[cfg(feature = "test-helpers")] impl AvailabilityChangeset { pub fn create_for_test( - date: &NaiveDate, + date: &chrono::NaiveDate, start_hour: u32, end_hour: u32, ) -> AvailabilityChangeset { diff --git a/db/src/models/event.rs b/db/src/models/event.rs index 7d255753..022a9af7 100644 --- a/db/src/models/event.rs +++ b/db/src/models/event.rs @@ -1,5 +1,5 @@ use chrono::{NaiveDate, NaiveDateTime}; -use sqlx::{PgPool, query}; +use sqlx::{PgPool, query, query_file}; use super::{Clothing, EventChangeset, Location, Result}; @@ -163,6 +163,51 @@ impl Event { Ok(events) } + pub async fn read_all_by_date_range_and_assigned_user_in_other_area( + pool: &PgPool, + date_range: (&NaiveDate, &NaiveDate), + user_id: i32, + area_to_ignore: i32, + ) -> Result> { + let records = query_file!( + "sql/event/read_all_by_date_range_and_assigned_user_in_other_area.sql", + user_id, + date_range.0, + date_range.1, + area_to_ignore + ) + .fetch_all(pool) + .await?; + + let events = records + .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, + fuehrungsassistent_required: record.fuehrungsassistentrequired, + amount_of_posten: record.amountofposten, + clothing: Clothing { + id: record.clothingid, + name: record.clothingname.clone(), + }, + canceled: record.canceled, + note: record.note.clone(), + }) + .collect(); + + Ok(events) + } + pub async fn read_by_id_including_location(pool: &PgPool, id: i32) -> Result> { let record = query!( r#" diff --git a/db/src/models/event_for_calendar.rs b/db/src/models/event_for_calendar.rs new file mode 100644 index 00000000..8dd015c0 --- /dev/null +++ b/db/src/models/event_for_calendar.rs @@ -0,0 +1,90 @@ +use chrono::NaiveDate; +use sqlx::{PgPool, query_file}; + +use super::{Area, Clothing, Event, Function, Location, Result, SimpleAssignment}; + +pub struct EventForCalendar { + pub event: Event, + pub assignments: Vec, + pub vehicle_assignments: Vec, +} + +impl EventForCalendar { + pub async fn read_all_by_date_and_area_and_user( + pool: &PgPool, + date: &NaiveDate, + area: i32, + user: i32, + ) -> Result> { + let records = query_file!( + "sql/event_for_calendar/read_all_by_date_and_area_and_user.sql", + date, + area, + user + ) + .fetch_all(pool) + .await?; + + let events = records + .iter() + .map(|record| EventForCalendar { + event: 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: Some(Area { + id: record.locationareaid, + name: record.areaname.clone(), + }), + }), + voluntary_wachhabender: record.voluntarywachhabender, + fuehrungsassistent_required: record.fuehrungsassistentrequired, + amount_of_posten: record.amountofposten, + clothing: Clothing { + id: record.clothingid, + name: record.clothingname.clone(), + }, + canceled: record.canceled, + note: record.note.clone(), + }, + assignments: record + .assignments + .as_ref() + .and_then(|f| Some(f.to_vec())) + .unwrap_or_default(), + vehicle_assignments: record + .vehicles + .as_ref() + .and_then(|f| Some(f.to_vec())) + .unwrap_or_default(), + }) + .collect(); + + Ok(events) + } + + pub fn get_wachhabender(&self) -> Option<&SimpleAssignment> { + self.assignments + .iter() + .find(|a| a.function == Function::Wachhabender) + } + + pub fn get_fuehrungsassistent(&self) -> Option<&SimpleAssignment> { + self.assignments + .iter() + .find(|a| a.function == Function::Fuehrungsassistent) + } + + pub fn get_posten(&self) -> Vec<&SimpleAssignment> { + self.assignments + .iter() + .filter(|a| a.function == Function::Posten) + .collect() + } +} diff --git a/db/src/models/export_event_row.rs b/db/src/models/export_event_row.rs index 176b69c4..8097fdbb 100644 --- a/db/src/models/export_event_row.rs +++ b/db/src/models/export_event_row.rs @@ -19,7 +19,7 @@ pub struct ExportEventRow { pub vehicles: Vec, } -#[derive(Debug, sqlx::Type)] +#[derive(Debug, sqlx::Type, Clone)] #[sqlx(type_name = "function", no_pg_array)] pub struct SimpleAssignment { pub name: String, diff --git a/db/src/models/mod.rs b/db/src/models/mod.rs index 7d2c6fa0..3880e929 100644 --- a/db/src/models/mod.rs +++ b/db/src/models/mod.rs @@ -7,6 +7,7 @@ mod availability_changeset; mod clothing; mod event; mod event_changeset; +mod event_for_calendar; mod export_event_row; mod function; mod location; @@ -30,6 +31,7 @@ pub use availability_changeset::{ pub use clothing::Clothing; pub use event::Event; pub use event_changeset::{EventChangeset, EventContext}; +pub use event_for_calendar::EventForCalendar; pub use export_event_row::{ExportEventRow, SimpleAssignment}; pub use function::Function; pub use location::Location; diff --git a/web/src/endpoints/availability/get_calendar.rs b/web/src/endpoints/availability/get_calendar.rs index 46a90b9e..4851c445 100644 --- a/web/src/endpoints/availability/get_calendar.rs +++ b/web/src/endpoints/availability/get_calendar.rs @@ -7,14 +7,13 @@ 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, + find_free_date_time_slots, Area, Availability, EventForCalendar, Role, User, }; #[derive(Deserialize)] @@ -31,13 +30,7 @@ struct CalendarTemplate { date: NaiveDate, selected_area: Option, areas: Vec, - events_and_assignments: Vec<( - Event, - Vec, - Option, - Option, - Vec, - )>, + events: Vec, availabilities: Vec, } @@ -83,66 +76,13 @@ async fn get( // !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( + let events = EventForCalendar::read_all_by_date_and_area_and_user( pool.get_ref(), - date, + &date, query.area.unwrap_or(user.area_id), + user.id, ) - .await? - .into_iter() - { - let assignments = Assignment::read_all_by_event(pool.get_ref(), e.id).await?; - let (posten, rest): (Vec, Vec) = assignments - .into_iter() - .partition(|a| a.function == Function::Posten); - let (wachhabender, fuehrungsassistent): (Vec, Vec) = 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, - )); - } + .await?; let template = CalendarTemplate { user: user.into_inner(), @@ -150,7 +90,7 @@ async fn get( date, selected_area, areas, - events_and_assignments, + events, availabilities, }; diff --git a/web/src/endpoints/availability/get_overview.rs b/web/src/endpoints/availability/get_overview.rs index 765935cd..131140c5 100644 --- a/web/src/endpoints/availability/get_overview.rs +++ b/web/src/endpoints/availability/get_overview.rs @@ -36,13 +36,24 @@ async fn get( let next_month = today.checked_add_months(Months::new(1)).unwrap(); let date_range = (&today, &next_month); - let events = Event::read_all_by_daterange_and_area_including_location( + let mut events = Event::read_all_by_daterange_and_area_including_location( pool.get_ref(), date_range, user.area_id, ) .await?; + let mut foreign_events = Event::read_all_by_date_range_and_assigned_user_in_other_area( + pool.get_ref(), + date_range, + user.id, + user.area_id, + ) + .await?; + + events.append(&mut foreign_events); + events.sort_by(|a, b| a.start.date().cmp(&b.start.date())); + let availabilities = Availability::read_all_by_user_and_daterange(pool.get_ref(), user.id, date_range).await?; diff --git a/web/templates/calendar.html b/web/templates/calendar.html index 7a8972e4..e0b951ed 100644 --- a/web/templates/calendar.html +++ b/web/templates/calendar.html @@ -146,12 +146,18 @@ {% endif %} - {% if events_and_assignments.len() == 0 %} + {% if events.len() == 0 %}
Keine Veranstaltungen geplant.
{% else %} - {% for (event, posten, fuehrungsassistent, wachhabender, vehicle) in events_and_assignments %} + {% for calendar_event in events %} + {%- let event = calendar_event.event -%} + {%- let location = calendar_event.event.location.as_ref().unwrap() -%} + {%- let wachhabender = calendar_event.get_wachhabender() -%} + {%- let fuehrungsassistent = calendar_event.get_fuehrungsassistent() -%} + {%- let posten = calendar_event.get_posten() -%} + {%- let vehicle = calendar_event.vehicle_assignments -%}