feat: WIP show foreign events in calendar
This commit is contained in:
parent
94143f54d7
commit
d88fe2cd3a
@ -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;
|
@ -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<SimpleAssignment>",
|
||||
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
|
||||
)
|
@ -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.",
|
||||
));
|
||||
|
@ -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 {
|
||||
|
@ -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<Vec<Event>> {
|
||||
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<Option<Event>> {
|
||||
let record = query!(
|
||||
r#"
|
||||
|
90
db/src/models/event_for_calendar.rs
Normal file
90
db/src/models/event_for_calendar.rs
Normal file
@ -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<SimpleAssignment>,
|
||||
pub vehicle_assignments: Vec<String>,
|
||||
}
|
||||
|
||||
impl EventForCalendar {
|
||||
pub async fn read_all_by_date_and_area_and_user(
|
||||
pool: &PgPool,
|
||||
date: &NaiveDate,
|
||||
area: i32,
|
||||
user: i32,
|
||||
) -> Result<Vec<EventForCalendar>> {
|
||||
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()
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ pub struct ExportEventRow {
|
||||
pub vehicles: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::Type)]
|
||||
#[derive(Debug, sqlx::Type, Clone)]
|
||||
#[sqlx(type_name = "function", no_pg_array)]
|
||||
pub struct SimpleAssignment {
|
||||
pub name: String,
|
||||
|
@ -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;
|
||||
|
@ -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<i32>,
|
||||
areas: Vec<Area>,
|
||||
events_and_assignments: Vec<(
|
||||
Event,
|
||||
Vec<String>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Vec<Vehicle>,
|
||||
)>,
|
||||
events: Vec<EventForCalendar>,
|
||||
availabilities: Vec<Availability>,
|
||||
}
|
||||
|
||||
@ -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<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,
|
||||
));
|
||||
}
|
||||
.await?;
|
||||
|
||||
let template = CalendarTemplate {
|
||||
user: user.into_inner(),
|
||||
@ -150,7 +90,7 @@ async fn get(
|
||||
date,
|
||||
selected_area,
|
||||
areas,
|
||||
events_and_assignments,
|
||||
events,
|
||||
availabilities,
|
||||
};
|
||||
|
||||
|
@ -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?;
|
||||
|
||||
|
@ -146,12 +146,18 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if events_and_assignments.len() == 0 %}
|
||||
{% if events.len() == 0 %}
|
||||
<div class="notification">
|
||||
Keine Veranstaltungen geplant.
|
||||
</div>
|
||||
{% 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 -%}
|
||||
<div class="box">
|
||||
<div class="fixed-grid has-1-cols-mobile">
|
||||
<div class="grid content">
|
||||
@ -159,7 +165,7 @@
|
||||
<h5 class="title is-5">{{ event.name }}</h5>
|
||||
</div>
|
||||
|
||||
{% if user.role == Role::AreaManager || user.role == Role::Admin %}
|
||||
{% if (user.role == Role::AreaManager && location.area_id == user.area_id) || user.role == Role::Admin %}
|
||||
<div class="cell is-narrow buttons is-justify-content-end mb-0">
|
||||
<a href="/events/{{ event.id }}/plan" hx-boost="true" class="button is-link is-light">
|
||||
<svg class="icon">
|
||||
@ -188,7 +194,10 @@
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<p><b>Veranstaltungsort:</b> {{ event.location.as_ref().unwrap().name }}</p>
|
||||
<p>
|
||||
<b>Veranstaltungsort:</b> {{ location.name }} {% if location.area_id != user.area_id %}(Fremdbereich {{
|
||||
location.area.as_ref().unwrap().name }}){% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
@ -222,19 +231,19 @@
|
||||
|
||||
{% if let Some(wh) = wachhabender %}
|
||||
<div class="cell">
|
||||
<p><b>Wachhabender geplant:</b> {{ wh }}</p>
|
||||
<p><b>Wachhabender geplant:</b> {{ wh.name }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if let Some(fa) = fuehrungsassistent %}
|
||||
<div class="cell">
|
||||
<p><b>Führungsassistent geplant:</b> {{ fa }}</p>
|
||||
<p><b>Führungsassistent geplant:</b> {{ fa.name }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if posten.len() > 0 %}
|
||||
<div class="cell is-col-span-2">
|
||||
<p><b>Posten:</b> {{ posten.join(", ")}}</p>
|
||||
<p><b>Posten:</b> {% for p in posten -%}{{ p.name }}{% if !loop.last %}, {% endif %}{% endfor %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -242,7 +251,7 @@
|
||||
<div class="cell is-col-span-2">
|
||||
<b>Fahrzeuge:</b>
|
||||
{% for v in vehicle %}
|
||||
<span class="tag is-link"> {{ v.radio_call_name }} - {{ v.station }}</span>
|
||||
<span class="tag is-link"> {{ v }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
Loading…
x
Reference in New Issue
Block a user