diff --git a/db/sql/availability/read_all_by_daterange_and_area_including_user_for_event_planning.sql b/db/sql/availability/read_all_by_daterange_and_area_including_user_for_event_planning.sql index 05a40cff..6cf2ec2a 100644 --- a/db/sql/availability/read_all_by_daterange_and_area_including_user_for_event_planning.sql +++ b/db/sql/availability/read_all_by_daterange_and_area_including_user_for_event_planning.sql @@ -14,12 +14,15 @@ SELECT user_.areaId, user_.locked, user_.lastLogin, - user_.receiveNotifications + user_.receiveNotifications, + area.name AS areaName FROM availability JOIN user_ ON availability.userId = user_.id +JOIN + area ON user_.areaId = area.id WHERE availability.starttimestamp::date = $1 - AND user_.areaId = $2 + AND (user_.areaId = $2 OR availability.crossAreal = true) AND availability.startTimestamp <= $3 AND availability.endTimestamp >= $4; diff --git a/db/src/models/availability.rs b/db/src/models/availability.rs index 5e8c5e31..12e938a9 100644 --- a/db/src/models/availability.rs +++ b/db/src/models/availability.rs @@ -18,7 +18,7 @@ impl Availability { pub async fn create( pool: &PgPool, user_id: i32, - changeset: AvailabilityChangeset, + changeset: &AvailabilityChangeset, ) -> Result<()> { query_file!( "sql/availability/create.sql", @@ -75,7 +75,7 @@ impl Availability { Ok(availabilities) } - /// loads availabilities for the area and the same day as the start date and which fully lie inside the daterange + /// loads availabilities for the area or ones that are cross-area and the same day as the start date and which fully lie inside the daterange pub async fn read_all_by_daterange_and_area_including_user_for_event_planning( pool: &PgPool, date_range: (NaiveDateTime, NaiveDateTime), @@ -105,7 +105,10 @@ impl Availability { role: r.role, function: r.function.clone(), area_id: r.areaid, - area: None, + area: Some(Area { + id: r.areaid, + name: r.areaname.clone(), + }), locked: r.locked, last_login: r.lastlogin, receive_notifications: r.receivenotifications, diff --git a/web/snapshots/brass_web__endpoints__assignment__post_new__tests__inner_response_produces_updated_template.snap b/web/snapshots/brass_web__endpoints__assignment__post_new__tests__inner_response_produces_updated_template.snap index 7cf8e584..3d97fd8e 100644 --- a/web/snapshots/brass_web__endpoints__assignment__post_new__tests__inner_response_produces_updated_template.snap +++ b/web/snapshots/brass_web__endpoints__assignment__post_new__tests__inner_response_produces_updated_template.snap @@ -15,8 +15,6 @@ snapshot_kind: text - - Max Mustermann diff --git a/web/snapshots/brass_web__endpoints__events__get_plan__tests__inner_generates_template_for_admin.snap b/web/snapshots/brass_web__endpoints__events__get_plan__tests__inner_generates_template_for_admin.snap new file mode 100644 index 00000000..c7dca92f --- /dev/null +++ b/web/snapshots/brass_web__endpoints__events__get_plan__tests__inner_generates_template_for_admin.snap @@ -0,0 +1,210 @@ +--- +source: web/src/endpoints/events/get_plan.rs +expression: body +snapshot_kind: text +--- +
+
+

Eventplanung

+ +
+
Allgemeines
+ +
+
+
+

Name: Große Veranstaltung

+
+ +
+

Datum: Mittwoch, 01.01.2025

+
+ +
+

Uhrzeit: 12:00 Uhr - 01.01.2025 22:00 Uhr

+
+ +
+

Veranstaltungsort: Location

+
+ +
+

Wachhabender: FF

+
+ +
+

Führungsassistent benötigt: ja

+
+ +
+

Anzahl der Posten: 5

+
+ +
+

Anzugsordnung: Tuchuniform

+
+ +
+

Anmerkungen:

+
+
+
+
+ +
+
Einteilung Personal
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameFunktionZeitraumKommentarPlanung
Max Mustermann +
Posten
+
+ 12:00 bis 01.01.2025 22:00 + + Kommentar + + + +
Rudi Tester (Fremdbereich Süd) +
Posten
+
+ 12:00 bis 01.01.2025 22:00 + + Kommentar + + + +
+
+ +
+
Einteilung Fahrzeuge
+
+
+ +
+
+ 11.49.1 - HLF FF Ost +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + +
+
diff --git a/web/src/endpoints/assignment/delete.rs b/web/src/endpoints/assignment/delete.rs index c53433e0..912e45a9 100644 --- a/web/src/endpoints/assignment/delete.rs +++ b/web/src/endpoints/assignment/delete.rs @@ -98,7 +98,7 @@ mod tests { ), comment: None, }; - Availability::create(pool, 1, new_availability).await?; + Availability::create(pool, 1, &new_availability).await?; let new_assignment = AssignmentChangeset { function: Function::Posten, diff --git a/web/src/endpoints/assignment/post_new.rs b/web/src/endpoints/assignment/post_new.rs index 7c0b0965..711d511b 100644 --- a/web/src/endpoints/assignment/post_new.rs +++ b/web/src/endpoints/assignment/post_new.rs @@ -117,7 +117,7 @@ mod tests { Availability::create( pool, 1, - AvailabilityChangeset { + &AvailabilityChangeset { time: (start, end), comment: None, }, @@ -335,7 +335,7 @@ mod tests { Availability::create( &context.db_pool, 1, - AvailabilityChangeset { + &AvailabilityChangeset { time: (start, end), comment: None, }, @@ -346,7 +346,7 @@ mod tests { Availability::create( &context.db_pool, 2, - AvailabilityChangeset { + &AvailabilityChangeset { time: (start, end), comment: None, }, diff --git a/web/src/endpoints/availability/delete.rs b/web/src/endpoints/availability/delete.rs index 15c18516..c55122b6 100644 --- a/web/src/endpoints/availability/delete.rs +++ b/web/src/endpoints/availability/delete.rs @@ -34,21 +34,23 @@ mod tests { use brass_macros::db_test; use chrono::NaiveDateTime; use fake::{Fake, Faker}; + use sqlx::PgPool; use crate::utils::test_helper::{ create_test_login_user, test_delete, DbTestContext, NaiveDateTimeExt, RequestConfig, }; - #[db_test] - async fn deletes_when_availability_is_from_user_itself(context: &DbTestContext) { - let app = context.app().await; - let config = RequestConfig::new("/availability/delete/1"); - create_test_login_user(&context.db_pool, &config).await; + async fn arrange(pool: &PgPool, availability_id: i32, area_id: i32) -> Result<(), sqlx::Error> { + Area::create(pool, "Süd").await?; + let mut changeset: UserChangeset = Faker.fake(); + changeset.area_id = area_id; + + User::create(pool, &changeset).await?; Availability::create( - &context.db_pool, - 1, - AvailabilityChangeset { + pool, + availability_id, + &AvailabilityChangeset { time: ( NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 10, 0, 0).unwrap(), NaiveDateTime::from_ymd_and_hms(2025, 02, 01, 10, 0, 0).unwrap(), @@ -56,8 +58,18 @@ mod tests { comment: None, }, ) - .await - .unwrap(); + .await?; + + Ok(()) + } + + #[db_test] + async fn deletes_when_availability_is_from_user_itself(context: &DbTestContext) { + let app = context.app().await; + let config = RequestConfig::new("/availability/delete/1"); + create_test_login_user(&context.db_pool, &config).await; + + arrange(&context.db_pool, 1, 1).await.unwrap(); let response = test_delete(&app, &config).await; assert_eq!(StatusCode::OK, response.status()); @@ -73,22 +85,7 @@ mod tests { let app = context.app().await; let config = RequestConfig::new("/availability/delete/1").with_role(Role::AreaManager); create_test_login_user(&context.db_pool, &config).await; - - User::create(&context.db_pool, &Faker.fake()).await.unwrap(); - - Availability::create( - &context.db_pool, - 2, - AvailabilityChangeset { - time: ( - NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 10, 0, 0).unwrap(), - NaiveDateTime::from_ymd_and_hms(2025, 02, 01, 10, 0, 0).unwrap(), - ), - comment: None, - }, - ) - .await - .unwrap(); + arrange(&context.db_pool, 2, 1).await.unwrap(); let response = test_delete(&app, &config).await; assert_eq!(StatusCode::OK, response.status()); @@ -104,26 +101,7 @@ mod tests { let app = context.app().await; let config = RequestConfig::new("/availability/delete/1").with_role(Role::AreaManager); create_test_login_user(&context.db_pool, &config).await; - - Area::create(&context.db_pool, "Süd").await.unwrap(); - let mut changeset: UserChangeset = Faker.fake(); - changeset.area_id = 2; - - User::create(&context.db_pool, &changeset).await.unwrap(); - - Availability::create( - &context.db_pool, - 2, - AvailabilityChangeset { - time: ( - NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 10, 0, 0).unwrap(), - NaiveDateTime::from_ymd_and_hms(2025, 02, 01, 10, 0, 0).unwrap(), - ), - comment: None, - }, - ) - .await - .unwrap(); + arrange(&context.db_pool, 2, 2).await.unwrap(); let response = test_delete(&app, &config).await; assert_eq!(StatusCode::UNAUTHORIZED, response.status()); @@ -139,22 +117,7 @@ mod tests { let app = context.app().await; let config = RequestConfig::new("/availability/delete/1").with_role(Role::Admin); create_test_login_user(&context.db_pool, &config).await; - - User::create(&context.db_pool, &Faker.fake()).await.unwrap(); - - Availability::create( - &context.db_pool, - 2, - AvailabilityChangeset { - time: ( - NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 10, 0, 0).unwrap(), - NaiveDateTime::from_ymd_and_hms(2025, 02, 01, 10, 0, 0).unwrap(), - ), - comment: None, - }, - ) - .await - .unwrap(); + arrange(&context.db_pool, 2, 1).await.unwrap(); let response = test_delete(&app, &config).await; assert_eq!(StatusCode::OK, response.status()); diff --git a/web/src/endpoints/availability/post_new.rs b/web/src/endpoints/availability/post_new.rs index a346a101..03d5c2aa 100644 --- a/web/src/endpoints/availability/post_new.rs +++ b/web/src/endpoints/availability/post_new.rs @@ -66,7 +66,7 @@ pub async fn post( Availability::update(pool.get_ref(), a.id, changeset).await?; } else { - Availability::create(pool.get_ref(), user_for_availability, changeset).await?; + Availability::create(pool.get_ref(), user_for_availability, &changeset).await?; } let url = utils::get_return_url_for_date(&form.startdate); diff --git a/web/src/endpoints/availability/put_cross_areal.rs b/web/src/endpoints/availability/put_cross_areal.rs index 2b61e01e..180119ec 100644 --- a/web/src/endpoints/availability/put_cross_areal.rs +++ b/web/src/endpoints/availability/put_cross_areal.rs @@ -106,7 +106,7 @@ mod tests { Availability::create( &context.db_pool, 2, - AvailabilityChangeset { + &AvailabilityChangeset { time: ( NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 10, 0, 0).unwrap(), NaiveDateTime::from_ymd_and_hms(2025, 02, 01, 10, 0, 0).unwrap(), @@ -169,7 +169,7 @@ mod tests { Availability::create( &context.db_pool, 2, - AvailabilityChangeset { + &AvailabilityChangeset { time: ( NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 10, 0, 0).unwrap(), NaiveDateTime::from_ymd_and_hms(2025, 02, 01, 10, 0, 0).unwrap(), @@ -216,7 +216,7 @@ mod tests { Availability::create( &context.db_pool, 1, - AvailabilityChangeset { + &AvailabilityChangeset { time: ( NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 10, 0, 0).unwrap(), NaiveDateTime::from_ymd_and_hms(2025, 02, 01, 10, 0, 0).unwrap(), @@ -267,7 +267,7 @@ mod tests { Availability::create( &context.db_pool, 2, - AvailabilityChangeset { + &AvailabilityChangeset { time: ( NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 10, 0, 0).unwrap(), NaiveDateTime::from_ymd_and_hms(2025, 02, 01, 10, 0, 0).unwrap(), diff --git a/web/src/endpoints/events/get_plan.rs b/web/src/endpoints/events/get_plan.rs index ec3c0f3c..098a18d9 100644 --- a/web/src/endpoints/events/get_plan.rs +++ b/web/src/endpoints/events/get_plan.rs @@ -18,7 +18,12 @@ use crate::{ use brass_db::models::{Availability, AvailabilityAssignmentState, Event, Role, User, Vehicle}; #[derive(Template)] -#[template(path = "events/plan.html")] +#[cfg_attr(not(test), template(path = "events/plan.html"))] +#[cfg_attr( + test, + template(path = "events/plan.html", block = "content"), + allow(dead_code) +)] pub struct PlanEventTemplate { user: User, event: Event, @@ -72,3 +77,132 @@ pub async fn get( Ok(template.to_response()?) } + +#[cfg(test)] +mod tests { + use actix_http::StatusCode; + use brass_macros::db_test; + use chrono::NaiveDateTime; + use fake::{Fake, Faker}; + use sqlx::PgPool; + + use crate::utils::test_helper::{ + assert_snapshot, create_test_login_user, test_get, DbTestContext, NaiveDateTimeExt, + RequestConfig, ServiceResponseExt, + }; + use brass_db::models::{ + Area, Assignment, AssignmentChangeset, Availability, AvailabilityChangeset, Event, + EventChangeset, Function, Location, Role, User, UserChangeset, Vehicle, VehicleAssignment, + }; + + #[db_test] + async fn generates_template_for_admin(context: &DbTestContext) { + let app = context.app().await; + let config = RequestConfig::new("/events/1/plan").with_role(Role::Admin); + arrange(&context.db_pool).await.unwrap(); + create_test_login_user(&context.db_pool, &config).await; + + let (status, body) = test_get(app, &config).await.into_status_and_body().await; + + assert_eq!(StatusCode::OK, status); + assert_snapshot!(body); + } + + #[db_test] + async fn returns_ok_for_area_manager_of_same_area(context: &DbTestContext) { + let app = context.app().await; + let config = RequestConfig::new("/events/1/plan").with_role(Role::AreaManager); + arrange(&context.db_pool).await.unwrap(); + create_test_login_user(&context.db_pool, &config).await; + + let response = test_get(app, &config).await; + + assert_eq!(StatusCode::OK, response.status()); + } + + #[db_test] + async fn returns_unauthorized_for_area_manager_of_different_area(context: &DbTestContext) { + let app = context.app().await; + let config = RequestConfig::new("/events/1/plan") + .with_role(Role::AreaManager) + .with_user_area(2); + arrange(&context.db_pool).await.unwrap(); + create_test_login_user(&context.db_pool, &config).await; + + let response = test_get(app, &config).await; + + assert_eq!(StatusCode::UNAUTHORIZED, response.status()); + } + + #[db_test] + async fn returns_unauthorized_for_user(context: &DbTestContext) { + let app = context.app().await; + let config = RequestConfig::new("/events/1/plan"); + arrange(&context.db_pool).await.unwrap(); + create_test_login_user(&context.db_pool, &config).await; + + let response = test_get(app, &config).await; + + assert_eq!(StatusCode::UNAUTHORIZED, response.status()); + } + + #[db_test] + async fn returns_not_found_when_event_doesnt_exist(context: &DbTestContext) { + let app = context.app().await; + let config = RequestConfig::new("/events/1/plan").with_role(Role::AreaManager); + create_test_login_user(&context.db_pool, &config).await; + + let response = test_get(app, &config).await; + + assert_eq!(StatusCode::NOT_FOUND, response.status()); + } + + async fn arrange(pool: &PgPool) -> anyhow::Result<()> { + Location::create(pool, "Location", 1).await?; + Area::create(pool, "Süd").await.unwrap(); + + let mut user_changeset: UserChangeset = Faker.fake(); + user_changeset.name = "Max Mustermann".to_string(); + User::create(pool, &user_changeset).await?; + + let mut other_area_user: UserChangeset = Faker.fake(); + other_area_user.area_id = 2; + other_area_user.name = "Rudi Tester".to_string(); + User::create(pool, &other_area_user).await?; + + let start = NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 12, 0, 0).unwrap(); + let end = NaiveDateTime::from_ymd_and_hms(2025, 01, 01, 22, 0, 0).unwrap(); + + let mut new_event = EventChangeset::create_for_test(start, end); + new_event.name = "Große Veranstaltung".to_string(); + Event::create(pool, new_event).await?; + + let new_availability = AvailabilityChangeset { + time: (start, end), + comment: Some("Kommentar".to_string()), + }; + Availability::create(pool, 1, &new_availability).await?; + Availability::create(pool, 2, &new_availability).await?; + Availability::update_cross_areal(pool, 2, true) + .await + .unwrap(); + + let new_assignment = AssignmentChangeset { + function: Function::Posten, + time: (start, end), + }; + Assignment::create(pool, 1, 1, new_assignment).await?; + + Vehicle::create(pool, "11.49.1", "HLF FF Ost") + .await + .unwrap(); + Vehicle::create(pool, "11.19.1", "MTW FF Ost") + .await + .unwrap(); + VehicleAssignment::create(pool, 1, 1, start, end) + .await + .unwrap(); + + Ok(()) + } +} diff --git a/web/templates/events/plan.html b/web/templates/events/plan.html index fa34ba56..37d0c890 100644 --- a/web/templates/events/plan.html +++ b/web/templates/events/plan.html @@ -32,7 +32,7 @@
-

Führungsassistent benötigt: {% if event.fuehrungsassistent_required %}ja{% else %}nein{% endif %} +

Führungsassistent benötigt: {% if event.fuehrungsassistent_required %}ja{% else %}nein{% endif -%}

diff --git a/web/templates/events/plan_personal_table.html b/web/templates/events/plan_personal_table.html index 88edb888..ea7702c9 100644 --- a/web/templates/events/plan_personal_table.html +++ b/web/templates/events/plan_personal_table.html @@ -19,10 +19,11 @@ - {% for (availability, status) in availabilities %} - {% let u = availability.user.as_ref().unwrap() %} + {% for (availability, status) in availabilities -%} + {%- let u = availability.user.as_ref().unwrap() -%} - {{ u.name }} + {{ u.name }}{% if u.area_id != event.location.as_ref().unwrap().area_id %} (Fremdbereich {{ u.area.as_ref().unwrap().name + }}){% endif %} {{ u.function|show_tree|safe }}