feat: vehicle assignment for event
This commit is contained in:
parent
954bba25d5
commit
366910ba0a
41
.sqlx/query-159c257e9e7a164d369de950940166706b5adf4815de81481c9eb67d94b7ee0d.json
generated
Normal file
41
.sqlx/query-159c257e9e7a164d369de950940166706b5adf4815de81481c9eb67d94b7ee0d.json
generated
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM vehicleAssignement WHERE vehicleAssignement.eventId = $1 AND vehicleAssignement.vehicleId = $2;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "eventid",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "vehicleid",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "starttime",
|
||||
"type_info": "Time"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "endtime",
|
||||
"type_info": "Time"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "159c257e9e7a164d369de950940166706b5adf4815de81481c9eb67d94b7ee0d"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n event.id AS eventId,\n event.date,\n event.startTime,\n event.endTime,\n event.name,\n event.locationId,\n event.voluntaryWachhabender,\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 FROM event\n JOIN location ON event.locationId = location.id\n WHERE date = $1\n AND location.areaId = $2;\n ",
|
||||
"query": "\n SELECT\n event.id AS eventId,\n event.date,\n event.startTime,\n event.endTime,\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 FROM event\n JOIN location ON event.locationId = location.id\n WHERE date = $1\n AND location.areaId = $2;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -40,36 +40,41 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "voluntaryfuehrungsassistent",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "amountofposten",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "clothing",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "canceled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "locationname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "locationareaid",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
@ -91,11 +96,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "efb827de66b93f6087ebfb49360abded5f7d5bef3b22db0fc3de466924b23782"
|
||||
"hash": "bc6f85f2d5712c00966319960ceb8a964ebeff7249904f42b2d7b47371a9aea5"
|
||||
}
|
32
.sqlx/query-c79fb8f25bf2e9f4299f690bebf7be5dc4e4ceacfc14b37472c39e65d9501be9.json
generated
Normal file
32
.sqlx/query-c79fb8f25bf2e9f4299f690bebf7be5dc4e4ceacfc14b37472c39e65d9501be9.json
generated
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT *\n FROM vehicle\n\n ;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "radiocallname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "station",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c79fb8f25bf2e9f4299f690bebf7be5dc4e4ceacfc14b37472c39e65d9501be9"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n event.id AS eventId,\n event.date,\n event.startTime,\n event.endTime,\n event.name,\n event.locationId,\n event.voluntaryWachhabender,\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 FROM event\n JOIN location ON event.locationId = location.id\n WHERE event.id = $1;\n ",
|
||||
"query": "\n SELECT\n event.id AS eventId,\n event.date,\n event.startTime,\n event.endTime,\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 FROM event\n JOIN location ON event.locationId = location.id\n WHERE event.id = $1;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -40,36 +40,41 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "voluntaryfuehrungsassistent",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "amountofposten",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "clothing",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "canceled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "note",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "locationname",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "locationareaid",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
@ -90,11 +95,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5c5c88811fd870d2f68b76fe71afd2ee1e72623c94be406e369af8a4a04591e0"
|
||||
"hash": "df9d7eeb0c4c2d5b222896589dc65699421fb09a26e505871b050e12ec6634c2"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO event (date, startTime, endTime, name, locationId, voluntaryWachhabender, amountOfPosten, clothing, note)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);\n ",
|
||||
"query": "\n INSERT INTO event (date, startTime, endTime, name, locationId, voluntaryWachhabender, voluntaryFuehrungsassistent, amountOfPosten, clothing, note)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@ -11,6 +11,7 @@
|
||||
"Text",
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Int2",
|
||||
"Text",
|
||||
"Text"
|
||||
@ -18,5 +19,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "57d4a852f0845c761990cc1483bfc3e6f2e8b130427b7cae9e2376b45625195f"
|
||||
"hash": "fae818e52e9e5cc9c38684afe5a08395e797fcdd19607cacfd1322037f3805c5"
|
||||
}
|
@ -5,10 +5,11 @@ use sqlx::PgPool;
|
||||
use crate::{
|
||||
endpoints::IdPath,
|
||||
filters,
|
||||
models::{Availabillity, AvailabillityAssignmentState, Event, Function, Role, User},
|
||||
models::{Availabillity, AvailabillityAssignmentState, Event, Function, Role, User, Vehicle},
|
||||
utils::{
|
||||
event_planning_template::{
|
||||
generate_availabillity_assignment_list, generate_status_whether_staff_is_required,
|
||||
generate_vehicles_assigned_and_available,
|
||||
},
|
||||
ApplicationError,
|
||||
},
|
||||
@ -23,6 +24,8 @@ pub struct PlanEventTemplate {
|
||||
further_posten_required: bool,
|
||||
further_fuehrungsassistent_required: bool,
|
||||
further_wachhabender_required: bool,
|
||||
vehicles_available: Vec<Vehicle>,
|
||||
vehicles_assigned: Vec<Vehicle>,
|
||||
}
|
||||
|
||||
#[actix_web::get("/events/{id}/plan")]
|
||||
@ -51,6 +54,9 @@ pub async fn get(
|
||||
further_wachhabender_required,
|
||||
) = generate_status_whether_staff_is_required(pool.get_ref(), &event).await?;
|
||||
|
||||
let (vehicles_assigned, vehicles_available) =
|
||||
generate_vehicles_assigned_and_available(pool.get_ref(), &event).await?;
|
||||
|
||||
let template = PlanEventTemplate {
|
||||
user: user.into_inner(),
|
||||
event,
|
||||
@ -58,6 +64,8 @@ pub async fn get(
|
||||
further_posten_required,
|
||||
further_fuehrungsassistent_required,
|
||||
further_wachhabender_required,
|
||||
vehicles_assigned,
|
||||
vehicles_available,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().body(template.render()?))
|
||||
|
@ -80,4 +80,7 @@ pub fn init(cfg: &mut ServiceConfig) {
|
||||
cfg.service(vehicle::get_overview::get);
|
||||
cfg.service(vehicle::post_new::post);
|
||||
cfg.service(vehicle::post_edit::post);
|
||||
|
||||
cfg.service(vehicle_assignment::post_new::post);
|
||||
cfg.service(vehicle_assignment::delete::delete);
|
||||
}
|
||||
|
59
web/src/endpoints/vehicle_assignment/delete.rs
Normal file
59
web/src/endpoints/vehicle_assignment/delete.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use rinja::Template;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
endpoints::vehicle_assignment::PlanVehiclesPartialTemplate,
|
||||
models::{Event, Role, User, VehicleAssignement},
|
||||
utils::{event_planning_template::generate_vehicles_assigned_and_available, ApplicationError},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct VehicleAssignmentDeleteQuery {
|
||||
vehicle: i32,
|
||||
event: i32,
|
||||
}
|
||||
|
||||
#[actix_web::delete("/vehicleassignments/delete")]
|
||||
pub async fn delete(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<VehicleAssignmentDeleteQuery>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
let Some(event) = Event::read_by_id_including_location(pool.get_ref(), query.event).await?
|
||||
else {
|
||||
return Ok(HttpResponse::NotFound().finish());
|
||||
};
|
||||
|
||||
let user_is_admin_or_area_manager_of_event_area = user.role == Role::Admin
|
||||
|| (user.role == Role::AreaManager
|
||||
&& user.area_id == event.location.as_ref().unwrap().area_id);
|
||||
|
||||
if !user_is_admin_or_area_manager_of_event_area {
|
||||
return Err(ApplicationError::Unauthorized);
|
||||
}
|
||||
|
||||
let Some(vehicle_assignment) =
|
||||
VehicleAssignement::read(pool.get_ref(), event.id, query.vehicle).await?
|
||||
else {
|
||||
return Ok(HttpResponse::NotFound().finish());
|
||||
};
|
||||
|
||||
VehicleAssignement::delete(
|
||||
pool.get_ref(),
|
||||
vehicle_assignment.event_id,
|
||||
vehicle_assignment.vehicle_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (vehicles_assigned, vehicles_available) =
|
||||
generate_vehicles_assigned_and_available(pool.get_ref(), &event).await?;
|
||||
let template = PlanVehiclesPartialTemplate {
|
||||
event,
|
||||
vehicles_assigned,
|
||||
vehicles_available,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().body(template.render()?))
|
||||
}
|
@ -1 +1,14 @@
|
||||
use rinja::Template;
|
||||
|
||||
use crate::models::{Event, Vehicle};
|
||||
|
||||
pub mod post_new;
|
||||
pub mod delete;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "events/plan_vehicles.html")]
|
||||
pub struct PlanVehiclesPartialTemplate {
|
||||
event: Event,
|
||||
vehicles_available: Vec<Vehicle>,
|
||||
vehicles_assigned: Vec<Vehicle>,
|
||||
}
|
||||
|
@ -4,11 +4,12 @@ use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
endpoints::assignment::PlanEventPersonalTablePartialTemplate,
|
||||
endpoints::{assignment::PlanEventPersonalTablePartialTemplate, vehicle_assignment::PlanVehiclesPartialTemplate},
|
||||
models::{Assignment, Availabillity, Event, Function, Role, User, Vehicle, VehicleAssignement},
|
||||
utils::{
|
||||
event_planning_template::{
|
||||
generate_availabillity_assignment_list, generate_status_whether_staff_is_required,
|
||||
generate_vehicles_assigned_and_available,
|
||||
},
|
||||
ApplicationError,
|
||||
},
|
||||
@ -16,14 +17,19 @@ use crate::{
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VehicleAssignmentQuery {
|
||||
vehicle: i32,
|
||||
event: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VehicleAssignmentForm {
|
||||
vehicle: i32,
|
||||
}
|
||||
|
||||
#[actix_web::post("/vehicleassignments/new")]
|
||||
pub async fn post(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
form: web::Form<VehicleAssignmentForm>,
|
||||
query: web::Query<VehicleAssignmentQuery>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
let Some(event) = Event::read_by_id_including_location(pool.get_ref(), query.event).await?
|
||||
@ -39,7 +45,7 @@ pub async fn post(
|
||||
return Err(ApplicationError::Unauthorized);
|
||||
}
|
||||
|
||||
let Some(vehicle) = Vehicle::read(pool.get_ref(), query.vehicle).await? else {
|
||||
let Some(vehicle) = Vehicle::read(pool.get_ref(), form.vehicle).await? else {
|
||||
return Ok(HttpResponse::NotFound().finish());
|
||||
};
|
||||
|
||||
@ -55,7 +61,8 @@ pub async fn post(
|
||||
.any(|a| has_start_time_during_event(a) || has_end_time_during_event(a));
|
||||
|
||||
if availability_already_assigned {
|
||||
return Ok(HttpResponse::BadRequest().body("Vehicle already assigned to a timely conflicting event."));
|
||||
return Ok(HttpResponse::BadRequest()
|
||||
.body("Vehicle already assigned to a timely conflicting event."));
|
||||
}
|
||||
|
||||
VehicleAssignement::create(
|
||||
@ -67,5 +74,13 @@ pub async fn post(
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(HttpResponse::NotFound().finish());
|
||||
let (vehicles_assigned, vehicles_available) =
|
||||
generate_vehicles_assigned_and_available(pool.get_ref(), &event).await?;
|
||||
let template = PlanVehiclesPartialTemplate {
|
||||
event,
|
||||
vehicles_assigned,
|
||||
vehicles_available,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().body(template.render()?))
|
||||
}
|
||||
|
@ -31,6 +31,24 @@ impl VehicleAssignement {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn read(
|
||||
pool: &PgPool,
|
||||
event_id: i32,
|
||||
vehicle_id: i32,
|
||||
) -> Result<Option<VehicleAssignement>> {
|
||||
let record = query!("SELECT * FROM vehicleAssignement WHERE vehicleAssignement.eventId = $1 AND vehicleAssignement.vehicleId = $2;", event_id, vehicle_id).fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
let vehicle_assignment = record.and_then(|r| Some(VehicleAssignement {
|
||||
event_id: r.eventid,
|
||||
vehicle_id: r.vehicleid,
|
||||
start_time: r.starttime,
|
||||
end_time: r.endtime,
|
||||
}));
|
||||
|
||||
Ok(vehicle_assignment)
|
||||
}
|
||||
|
||||
pub async fn read_all_by_event(
|
||||
pool: &PgPool,
|
||||
event_id: i32,
|
||||
|
@ -1,6 +1,8 @@
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::models::{Assignment, Availabillity, AvailabillityAssignmentState, Event, Function};
|
||||
use crate::models::{
|
||||
Assignment, Availabillity, AvailabillityAssignmentState, Event, Function, Vehicle, VehicleAssignement,
|
||||
};
|
||||
|
||||
use super::ApplicationError;
|
||||
|
||||
@ -90,3 +92,20 @@ pub async fn generate_status_whether_staff_is_required(
|
||||
further_wachhabender_required,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn generate_vehicles_assigned_and_available(
|
||||
pool: &PgPool,
|
||||
event: &Event,
|
||||
) -> Result<(Vec<Vehicle>, Vec<Vehicle>), ApplicationError> {
|
||||
let all_vehicles = Vehicle::read_all(pool).await?;
|
||||
let existing_vehicle_assignments_for_event =
|
||||
VehicleAssignement::read_all_by_event(pool, event.id).await?;
|
||||
let (vehicles_assigned, vehicles_available): (Vec<Vehicle>, Vec<Vehicle>) =
|
||||
all_vehicles.into_iter().partition(|v| {
|
||||
existing_vehicle_assignments_for_event
|
||||
.iter()
|
||||
.any(|va| va.vehicle_id == v.id)
|
||||
});
|
||||
|
||||
Ok((vehicles_assigned, vehicles_available))
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ $primary: $crimson,
|
||||
@forward "bulma/sass/elements/tag";
|
||||
@forward "bulma/sass/elements/table";
|
||||
@forward "bulma/sass/elements/title";
|
||||
@forward "bulma/sass/elements/delete";
|
||||
|
||||
@forward "bulma/sass/form";
|
||||
|
||||
|
@ -56,7 +56,9 @@
|
||||
|
||||
<div class="box">
|
||||
<h5 class="title is-5">Einteilung Fahrzeuge</h5>
|
||||
|
||||
<div id="vehicle-plan">
|
||||
{% include "plan_vehicles.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
25
web/templates/events/plan_vehicles.html
Normal file
25
web/templates/events/plan_vehicles.html
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="field is-grouped is-grouped-multiline">
|
||||
{% for v in vehicles_assigned %}
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag is-link"> {{ v.radio_call_name }} - {{ v.station }}</span>
|
||||
<button class="tag is-delete" hx-delete="/vehicleassignments/delete?event={{ event.id }}&vehicle={{ v.id }}"
|
||||
hx-target="#vehicle-plan" />
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Fahrzeug hinzufügen</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select name="vehicle" hx-post="/vehicleassignments/new?event={{ event.id }}" hx-include="this"
|
||||
hx-target="#vehicle-plan">
|
||||
<option selected></option>
|
||||
{% for v in vehicles_available %}
|
||||
<option value="{{ v.id }}">{{ v.radio_call_name }} - {{ v.station }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Loading…
x
Reference in New Issue
Block a user