Merge pull request 'add trailer reservation funcitonality; Fixes #443' (#588) from trailer-reservation into main
Reviewed-on: #588
This commit is contained in:
commit
0e2ef9e256
@ -195,3 +195,21 @@ CREATE TABLE IF NOT EXISTS "weather" (
|
|||||||
"wind_gust" FLOAT NOT NULL,
|
"wind_gust" FLOAT NOT NULL,
|
||||||
"rain_mm" FLOAT NOT NULL
|
"rain_mm" FLOAT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "trailer" (
|
||||||
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"name" text NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "trailer_reservation" (
|
||||||
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"trailer_id" INTEGER NOT NULL REFERENCES boat(id),
|
||||||
|
"start_date" DATE NOT NULL,
|
||||||
|
"end_date" DATE NOT NULL,
|
||||||
|
"time_desc" TEXT NOT NULL,
|
||||||
|
"usage" TEXT NOT NULL,
|
||||||
|
"user_id_applicant" INTEGER NOT NULL REFERENCES user(id),
|
||||||
|
"user_id_confirmation" INTEGER REFERENCES user(id),
|
||||||
|
"created_at" datetime not null default CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
@ -64,3 +64,5 @@ INSERT INTO "rower" (logbook_id, rower_id) VALUES(3,3);
|
|||||||
INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at) VALUES(4,'Dolle bei Position 2 fehlt', 5, '2142-12-24 15:02');
|
INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at) VALUES(4,'Dolle bei Position 2 fehlt', 5, '2142-12-24 15:02');
|
||||||
INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at, lock_boat) VALUES(5, 'TOHT', 5, '2142-12-24 15:02', 1);
|
INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at, lock_boat) VALUES(5, 'TOHT', 5, '2142-12-24 15:02', 1);
|
||||||
INSERT INTO "notification" (user_id, message, category) VALUES (1, 'This is a test notification', 'test-cat');
|
INSERT INTO "notification" (user_id, message, category) VALUES (1, 'This is a test notification', 'test-cat');
|
||||||
|
INSERT INTO "trailer" (name) VALUES('Großer Hänger');
|
||||||
|
INSERT INTO "trailer" (name) VALUES('Kleiner Hänger');
|
||||||
|
@ -25,6 +25,8 @@ pub mod notification;
|
|||||||
pub mod role;
|
pub mod role;
|
||||||
pub mod rower;
|
pub mod rower;
|
||||||
pub mod stat;
|
pub mod stat;
|
||||||
|
pub mod trailer;
|
||||||
|
pub mod trailerreservation;
|
||||||
pub mod trip;
|
pub mod trip;
|
||||||
pub mod tripdetails;
|
pub mod tripdetails;
|
||||||
pub mod triptype;
|
pub mod triptype;
|
||||||
|
31
src/model/trailer.rs
Normal file
31
src/model/trailer.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
||||||
|
|
||||||
|
#[derive(FromRow, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Clone)]
|
||||||
|
pub struct Trailer {
|
||||||
|
pub id: i64,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Trailer {
|
||||||
|
pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
|
||||||
|
sqlx::query_as!(Self, "SELECT id, name FROM trailer WHERE id like ?", id)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, id: i32) -> Option<Self> {
|
||||||
|
sqlx::query_as!(Self, "SELECT id, name FROM trailer WHERE id like ?", id)
|
||||||
|
.fetch_one(db.deref_mut())
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
pub async fn all(db: &SqlitePool) -> Vec<Self> {
|
||||||
|
sqlx::query_as!(Self, "SELECT id, name FROM trailer")
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
233
src/model/trailerreservation.rs
Normal file
233
src/model/trailerreservation.rs
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{FromRow, SqlitePool};
|
||||||
|
|
||||||
|
use super::log::Log;
|
||||||
|
use super::notification::Notification;
|
||||||
|
use super::role::Role;
|
||||||
|
use super::trailer::Trailer;
|
||||||
|
use super::user::User;
|
||||||
|
use crate::tera::trailerreservation::ReservationEditForm;
|
||||||
|
|
||||||
|
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct TrailerReservation {
|
||||||
|
pub id: i64,
|
||||||
|
pub trailer_id: i64,
|
||||||
|
pub start_date: NaiveDate,
|
||||||
|
pub end_date: NaiveDate,
|
||||||
|
pub time_desc: String,
|
||||||
|
pub usage: String,
|
||||||
|
pub user_id_applicant: i64,
|
||||||
|
pub user_id_confirmation: Option<i64>,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct TrailerReservationWithDetails {
|
||||||
|
#[serde(flatten)]
|
||||||
|
reservation: TrailerReservation,
|
||||||
|
trailer: Trailer,
|
||||||
|
user_applicant: User,
|
||||||
|
user_confirmation: Option<User>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TrailerReservationToAdd<'r> {
|
||||||
|
pub trailer: &'r Trailer,
|
||||||
|
pub start_date: NaiveDate,
|
||||||
|
pub end_date: NaiveDate,
|
||||||
|
pub time_desc: &'r str,
|
||||||
|
pub usage: &'r str,
|
||||||
|
pub user_applicant: &'r User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrailerReservation {
|
||||||
|
pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
"SELECT id, trailer_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at
|
||||||
|
FROM trailer_reservation
|
||||||
|
WHERE id like ?",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn all_future(db: &SqlitePool) -> Vec<TrailerReservationWithDetails> {
|
||||||
|
let trailerreservations = sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
"
|
||||||
|
SELECT id, trailer_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at
|
||||||
|
FROM trailer_reservation
|
||||||
|
WHERE end_date >= CURRENT_DATE ORDER BY end_date
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap(); //TODO: fixme
|
||||||
|
|
||||||
|
let mut res = Vec::new();
|
||||||
|
for reservation in trailerreservations {
|
||||||
|
let user_confirmation = match reservation.user_id_confirmation {
|
||||||
|
Some(id) => {
|
||||||
|
let user = User::find_by_id(db, id as i32).await;
|
||||||
|
Some(user.unwrap())
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let user_applicant = User::find_by_id(db, reservation.user_id_applicant as i32)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let trailer = Trailer::find_by_id(db, reservation.trailer_id as i32)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
res.push(TrailerReservationWithDetails {
|
||||||
|
reservation,
|
||||||
|
trailer,
|
||||||
|
user_applicant,
|
||||||
|
user_confirmation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
pub async fn all_future_with_groups(
|
||||||
|
db: &SqlitePool,
|
||||||
|
) -> HashMap<String, Vec<TrailerReservationWithDetails>> {
|
||||||
|
let mut grouped_reservations: HashMap<String, Vec<TrailerReservationWithDetails>> =
|
||||||
|
HashMap::new();
|
||||||
|
|
||||||
|
let reservations = Self::all_future(db).await;
|
||||||
|
for reservation in reservations {
|
||||||
|
let key = format!(
|
||||||
|
"{}-{}-{}-{}-{}",
|
||||||
|
reservation.reservation.start_date,
|
||||||
|
reservation.reservation.end_date,
|
||||||
|
reservation.reservation.time_desc,
|
||||||
|
reservation.reservation.usage,
|
||||||
|
reservation.user_applicant.name
|
||||||
|
);
|
||||||
|
|
||||||
|
grouped_reservations
|
||||||
|
.entry(key)
|
||||||
|
.or_default()
|
||||||
|
.push(reservation);
|
||||||
|
}
|
||||||
|
|
||||||
|
grouped_reservations
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create(
|
||||||
|
db: &SqlitePool,
|
||||||
|
trailerreservation: TrailerReservationToAdd<'_>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
if Self::trailer_reserved_between_dates(
|
||||||
|
db,
|
||||||
|
trailerreservation.trailer,
|
||||||
|
&trailerreservation.start_date,
|
||||||
|
&trailerreservation.end_date,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Err("Hänger in diesem Zeitraum bereits reserviert.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::create(
|
||||||
|
db,
|
||||||
|
format!("New trailer reservation: {trailerreservation:?}"),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO trailer_reservation(trailer_id, start_date, end_date, time_desc, usage, user_id_applicant) VALUES (?,?,?,?,?,?)",
|
||||||
|
trailerreservation.trailer.id,
|
||||||
|
trailerreservation.start_date,
|
||||||
|
trailerreservation.end_date,
|
||||||
|
trailerreservation.time_desc,
|
||||||
|
trailerreservation.usage,
|
||||||
|
trailerreservation.user_applicant.id,
|
||||||
|
)
|
||||||
|
.execute(db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let board =
|
||||||
|
User::all_with_role(db, &Role::find_by_name(db, "Vorstand").await.unwrap()).await;
|
||||||
|
for user in board {
|
||||||
|
let date = if trailerreservation.start_date == trailerreservation.end_date {
|
||||||
|
format!("am {}", trailerreservation.start_date)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"von {} bis {}",
|
||||||
|
trailerreservation.start_date, trailerreservation.end_date
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Notification::create(
|
||||||
|
db,
|
||||||
|
&user,
|
||||||
|
&format!(
|
||||||
|
"{} hat eine neue Hängerreservierung für Hänger '{}' {} angelegt. Zeit: {}; Zweck: {}",
|
||||||
|
trailerreservation.user_applicant.name,
|
||||||
|
trailerreservation.trailer.name,
|
||||||
|
date,
|
||||||
|
trailerreservation.time_desc,
|
||||||
|
trailerreservation.usage
|
||||||
|
),
|
||||||
|
"Neue Hängerreservierung",
|
||||||
|
None,None
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn trailer_reserved_between_dates(
|
||||||
|
db: &SqlitePool,
|
||||||
|
trailer: &Trailer,
|
||||||
|
start_date: &NaiveDate,
|
||||||
|
end_date: &NaiveDate,
|
||||||
|
) -> bool {
|
||||||
|
sqlx::query!(
|
||||||
|
"SELECT COUNT(*) AS reservation_count
|
||||||
|
FROM trailer_reservation
|
||||||
|
WHERE trailer_id = ?
|
||||||
|
AND start_date <= ? AND end_date >= ?;",
|
||||||
|
trailer.id,
|
||||||
|
end_date,
|
||||||
|
start_date
|
||||||
|
)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.reservation_count
|
||||||
|
> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(&self, db: &SqlitePool, data: ReservationEditForm) {
|
||||||
|
let time_desc = data.time_desc.trim();
|
||||||
|
let usage = data.usage.trim();
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE trailer_reservation SET time_desc = ?, usage = ? where id = ?",
|
||||||
|
time_desc,
|
||||||
|
usage,
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
.execute(db)
|
||||||
|
.await
|
||||||
|
.unwrap(); //Okay, because we can only create a User of a valid id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&self, db: &SqlitePool) {
|
||||||
|
sqlx::query!("DELETE FROM trailer_reservation WHERE id=?", self.id)
|
||||||
|
.execute(db)
|
||||||
|
.await
|
||||||
|
.unwrap(); //Okay, because we can only create a Boat of a valid id
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,7 @@ mod misc;
|
|||||||
mod notification;
|
mod notification;
|
||||||
mod planned;
|
mod planned;
|
||||||
mod stat;
|
mod stat;
|
||||||
|
pub(crate) mod trailerreservation;
|
||||||
|
|
||||||
#[derive(FromForm, Debug)]
|
#[derive(FromForm, Debug)]
|
||||||
struct LoginForm<'r> {
|
struct LoginForm<'r> {
|
||||||
@ -200,6 +201,7 @@ pub fn config(rocket: Rocket<Build>) -> Rocket<Build> {
|
|||||||
.mount("/stat", stat::routes())
|
.mount("/stat", stat::routes())
|
||||||
.mount("/boatdamage", boatdamage::routes())
|
.mount("/boatdamage", boatdamage::routes())
|
||||||
.mount("/boatreservation", boatreservation::routes())
|
.mount("/boatreservation", boatreservation::routes())
|
||||||
|
.mount("/trailerreservation", trailerreservation::routes())
|
||||||
.mount("/cox", cox::routes())
|
.mount("/cox", cox::routes())
|
||||||
.mount("/admin", admin::routes())
|
.mount("/admin", admin::routes())
|
||||||
.mount("/board", board::routes())
|
.mount("/board", board::routes())
|
||||||
|
211
src/tera/trailerreservation.rs
Normal file
211
src/tera/trailerreservation.rs
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
use chrono::NaiveDate;
|
||||||
|
use rocket::{
|
||||||
|
form::Form,
|
||||||
|
get, post,
|
||||||
|
request::FlashMessage,
|
||||||
|
response::{Flash, Redirect},
|
||||||
|
routes, FromForm, Route, State,
|
||||||
|
};
|
||||||
|
use rocket_dyn_templates::Template;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
use tera::Context;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
model::{
|
||||||
|
log::Log,
|
||||||
|
trailer::Trailer,
|
||||||
|
trailerreservation::{TrailerReservation, TrailerReservationToAdd},
|
||||||
|
user::{DonauLinzUser, User, UserWithDetails},
|
||||||
|
},
|
||||||
|
tera::log::KioskCookie,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn index_kiosk(
|
||||||
|
db: &State<SqlitePool>,
|
||||||
|
flash: Option<FlashMessage<'_>>,
|
||||||
|
_kiosk: KioskCookie,
|
||||||
|
) -> Template {
|
||||||
|
let trailerreservations = TrailerReservation::all_future(db).await;
|
||||||
|
|
||||||
|
let mut context = Context::new();
|
||||||
|
if let Some(msg) = flash {
|
||||||
|
context.insert("flash", &msg.into_inner());
|
||||||
|
}
|
||||||
|
|
||||||
|
context.insert("trailerreservations", &trailerreservations);
|
||||||
|
context.insert("trailers", &Trailer::all(db).await);
|
||||||
|
context.insert("user", &User::all(db).await);
|
||||||
|
context.insert("show_kiosk_header", &true);
|
||||||
|
|
||||||
|
Template::render("trailerreservations", context.into_json())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/", rank = 2)]
|
||||||
|
async fn index(
|
||||||
|
db: &State<SqlitePool>,
|
||||||
|
flash: Option<FlashMessage<'_>>,
|
||||||
|
user: DonauLinzUser,
|
||||||
|
) -> Template {
|
||||||
|
let trailerreservations = TrailerReservation::all_future(db).await;
|
||||||
|
|
||||||
|
let mut context = Context::new();
|
||||||
|
if let Some(msg) = flash {
|
||||||
|
context.insert("flash", &msg.into_inner());
|
||||||
|
}
|
||||||
|
|
||||||
|
context.insert("trailerreservations", &trailerreservations);
|
||||||
|
context.insert("trailers", &Trailer::all(db).await);
|
||||||
|
context.insert("user", &User::all(db).await);
|
||||||
|
context.insert(
|
||||||
|
"loggedin_user",
|
||||||
|
&UserWithDetails::from_user(user.into(), db).await,
|
||||||
|
);
|
||||||
|
|
||||||
|
Template::render("trailerreservations", context.into_json())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct FormTrailerReservationToAdd<'r> {
|
||||||
|
pub trailer_id: i64,
|
||||||
|
pub start_date: &'r str,
|
||||||
|
pub end_date: &'r str,
|
||||||
|
pub time_desc: &'r str,
|
||||||
|
pub usage: &'r str,
|
||||||
|
pub user_id_applicant: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/new", data = "<data>", rank = 2)]
|
||||||
|
async fn create<'r>(
|
||||||
|
db: &State<SqlitePool>,
|
||||||
|
data: Form<FormTrailerReservationToAdd<'r>>,
|
||||||
|
user: DonauLinzUser,
|
||||||
|
) -> Flash<Redirect> {
|
||||||
|
let user_applicant: User = user.into();
|
||||||
|
let trailer = Trailer::find_by_id(db, data.trailer_id as i32)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let trailerreservation_to_add = TrailerReservationToAdd {
|
||||||
|
trailer: &trailer,
|
||||||
|
start_date: NaiveDate::parse_from_str(data.start_date, "%Y-%m-%d").unwrap(),
|
||||||
|
end_date: NaiveDate::parse_from_str(data.end_date, "%Y-%m-%d").unwrap(),
|
||||||
|
time_desc: data.time_desc,
|
||||||
|
usage: data.usage,
|
||||||
|
user_applicant: &user_applicant,
|
||||||
|
};
|
||||||
|
match TrailerReservation::create(db, trailerreservation_to_add).await {
|
||||||
|
Ok(_) => Flash::success(
|
||||||
|
Redirect::to("/trailerreservation"),
|
||||||
|
"Reservierung erfolgreich hinzugefügt",
|
||||||
|
),
|
||||||
|
Err(e) => Flash::error(Redirect::to("/trailerreservation"), format!("Fehler: {e}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/new", data = "<data>")]
|
||||||
|
async fn create_from_kiosk<'r>(
|
||||||
|
db: &State<SqlitePool>,
|
||||||
|
data: Form<FormTrailerReservationToAdd<'r>>,
|
||||||
|
_kiosk: KioskCookie,
|
||||||
|
) -> Flash<Redirect> {
|
||||||
|
let user_applicant: User = User::find_by_id(db, data.user_id_applicant.unwrap() as i32)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let trailer = Trailer::find_by_id(db, data.trailer_id as i32)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let trailerreservation_to_add = TrailerReservationToAdd {
|
||||||
|
trailer: &trailer,
|
||||||
|
start_date: NaiveDate::parse_from_str(data.start_date, "%Y-%m-%d").unwrap(),
|
||||||
|
end_date: NaiveDate::parse_from_str(data.end_date, "%Y-%m-%d").unwrap(),
|
||||||
|
time_desc: data.time_desc,
|
||||||
|
usage: data.usage,
|
||||||
|
user_applicant: &user_applicant,
|
||||||
|
};
|
||||||
|
match TrailerReservation::create(db, trailerreservation_to_add).await {
|
||||||
|
Ok(_) => Flash::success(
|
||||||
|
Redirect::to("/trailerreservation"),
|
||||||
|
"Reservierung erfolgreich hinzugefügt",
|
||||||
|
),
|
||||||
|
Err(e) => Flash::error(Redirect::to("/trailerreservation"), format!("Fehler: {e}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm, Debug)]
|
||||||
|
pub struct ReservationEditForm {
|
||||||
|
pub(crate) id: i32,
|
||||||
|
pub(crate) time_desc: String,
|
||||||
|
pub(crate) usage: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/", data = "<data>")]
|
||||||
|
async fn update(
|
||||||
|
db: &State<SqlitePool>,
|
||||||
|
data: Form<ReservationEditForm>,
|
||||||
|
user: User,
|
||||||
|
) -> Flash<Redirect> {
|
||||||
|
let Some(reservation) = TrailerReservation::find_by_id(db, data.id).await else {
|
||||||
|
return Flash::error(
|
||||||
|
Redirect::to("/trailerreservation"),
|
||||||
|
format!("Reservation with ID {} does not exist!", data.id),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if user.id != reservation.user_id_applicant && !user.has_role(db, "admin").await {
|
||||||
|
return Flash::error(
|
||||||
|
Redirect::to("/trailerreservation"),
|
||||||
|
"Not allowed to update reservation (only admins + creator do so).".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::create(
|
||||||
|
db,
|
||||||
|
format!(
|
||||||
|
"{} updated reservation from {reservation:?} to {data:?}",
|
||||||
|
user.name
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
reservation.update(db, data.into_inner()).await;
|
||||||
|
|
||||||
|
Flash::success(
|
||||||
|
Redirect::to("/trailerreservation"),
|
||||||
|
"Reservierung erfolgreich bearbeitet",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/<reservation_id>/delete")]
|
||||||
|
async fn delete<'r>(
|
||||||
|
db: &State<SqlitePool>,
|
||||||
|
reservation_id: i32,
|
||||||
|
user: DonauLinzUser,
|
||||||
|
) -> Flash<Redirect> {
|
||||||
|
let reservation = TrailerReservation::find_by_id(db, reservation_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if user.id == reservation.user_id_applicant || user.has_role(db, "admin").await {
|
||||||
|
reservation.delete(db).await;
|
||||||
|
Flash::success(
|
||||||
|
Redirect::to("/trailerreservation"),
|
||||||
|
"Reservierung erfolgreich gelöscht",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Flash::error(
|
||||||
|
Redirect::to("/trailerreservation"),
|
||||||
|
"Nur der Reservierer darf die Reservierung löschen.".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<Route> {
|
||||||
|
routes![
|
||||||
|
index,
|
||||||
|
index_kiosk,
|
||||||
|
create,
|
||||||
|
create_from_kiosk,
|
||||||
|
delete,
|
||||||
|
update
|
||||||
|
]
|
||||||
|
}
|
@ -36,6 +36,7 @@
|
|||||||
<a href="/stat/boats" class="px-2">Bootsauswertung</a>
|
<a href="/stat/boats" class="px-2">Bootsauswertung</a>
|
||||||
<a href="/boatdamage" class="px-2">Bootsschaden</a>
|
<a href="/boatdamage" class="px-2">Bootsschaden</a>
|
||||||
<a href="/boatreservation" class="px-2">Bootsreservierung</a>
|
<a href="/boatreservation" class="px-2">Bootsreservierung</a>
|
||||||
|
<a href="/trailerreservation" class="px-2">Hängerreservierung</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -78,6 +78,8 @@
|
|||||||
class="block w-100 py-2 hover:text-primary-600 border-t">Bootsschaden</a>
|
class="block w-100 py-2 hover:text-primary-600 border-t">Bootsschaden</a>
|
||||||
<a href="/boatreservation"
|
<a href="/boatreservation"
|
||||||
class="block w-100 py-2 hover:text-primary-600 border-t">Bootsreservierung</a>
|
class="block w-100 py-2 hover:text-primary-600 border-t">Bootsreservierung</a>
|
||||||
|
<a href="/trailerreservation"
|
||||||
|
class="block w-100 py-2 hover:text-primary-600 border-t">Hängerreservierung</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %}
|
{% if loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %}
|
||||||
<a href="/ergo" class="block w-100 py-2 hover:text-primary-600 border-t">Ergo</a>
|
<a href="/ergo" class="block w-100 py-2 hover:text-primary-600 border-t">Ergo</a>
|
||||||
|
@ -90,6 +90,10 @@
|
|||||||
<a href="/boatreservation"
|
<a href="/boatreservation"
|
||||||
class="block w-100 py-2 hover:text-primary-600">Bootsreservierung</a>
|
class="block w-100 py-2 hover:text-primary-600">Bootsreservierung</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="py-1">
|
||||||
|
<a href="/trailerreservation"
|
||||||
|
class="block w-100 py-2 hover:text-primary-600">Hängerreservierung</a>
|
||||||
|
</li>
|
||||||
<li class="py-1">
|
<li class="py-1">
|
||||||
<a href="/steering" class="block w-100 py-2 hover:text-primary-600">Steuerleute & Co</a>
|
<a href="/steering" class="block w-100 py-2 hover:text-primary-600">Steuerleute & Co</a>
|
||||||
</li>
|
</li>
|
||||||
|
98
templates/trailerreservations.html.tera
Normal file
98
templates/trailerreservations.html.tera
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
{% import "includes/macros" as macros %}
|
||||||
|
{% import "includes/forms/log" as log %}
|
||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="max-w-screen-lg w-full">
|
||||||
|
<h1 class="h1">Hängerreservierungen</h1>
|
||||||
|
<h2 class="text-md font-bold tracking-wide bg-primary-900 mt-3 p-3 text-white flex justify-between items-center rounded-md">
|
||||||
|
Neue Reservierung
|
||||||
|
<a href="#"
|
||||||
|
class="inline-flex justify-center rounded-md bg-primary-600 mx-1 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer"
|
||||||
|
data-sidebar="true"
|
||||||
|
data-trigger="sidebar"
|
||||||
|
data-header="Neue Reservierung anlegen"
|
||||||
|
data-body="#new-reservation">
|
||||||
|
{% include "includes/plus-icon" %}
|
||||||
|
<span class="sr-only">Neue Reservierung eintragen</span>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<div class="hidden">
|
||||||
|
<div id="new-reservation">
|
||||||
|
<form action="/trailerreservation/new" method="post" class="grid gap-3">
|
||||||
|
{{ macros::select(label="Anhänger", data=trailers, name="trailer_id", id="trailer_id", display=["name"], wrapper_class="col-span-4", nonSelectableDefault=" -- Wähle einen Hänger aus ---") }}
|
||||||
|
{% if not loggedin_user %}{{ macros::select(label='Reserviert von', data=user, name='user_id_applicant') }}{% endif %}
|
||||||
|
{{ macros::input(label='Beginn', name='start_date', type='date', required=true, wrapper_class='col-span-4') }}
|
||||||
|
{{ macros::input(label='Ende', name='end_date', type='date', required=true, wrapper_class='col-span-4') }}
|
||||||
|
{{ macros::input(label='Uhrzeit (zB ab 14:00 Uhr, ganztägig, ...)', name='time_desc', type='text', required=true, wrapper_class='col-span-4') }}
|
||||||
|
{{ macros::input(label='Zweck (Wanderfahrt, ...)', name='usage', type='text', required=true, wrapper_class='col-span-4') }}
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary w-full col-span-4"
|
||||||
|
value="Reservierung eintragen" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="search-wrapper">
|
||||||
|
<label for="name" class="sr-only">Suche</label>
|
||||||
|
<input type="search"
|
||||||
|
name="name"
|
||||||
|
id="filter-js"
|
||||||
|
class="search-bar"
|
||||||
|
placeholder="Suchen nach Namen...">
|
||||||
|
</div>
|
||||||
|
<div id="filter-result-js" class="search-result"></div>
|
||||||
|
{% for reservation in trailerreservations %}
|
||||||
|
{% set allowed_to_edit = false %}
|
||||||
|
{% if loggedin_user %}
|
||||||
|
{% if loggedin_user.id == reservation.user_applicant.id or "admin" in loggedin_user.roles %}
|
||||||
|
{% set allowed_to_edit = true %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<div data-filterable="true"
|
||||||
|
data-filter="{{ reservation.user_applicant.name }} {{ reservation.trailer.name }}"
|
||||||
|
class="w-full border-t bg-white dark:bg-primary-900 text-black dark:text-white p-3">
|
||||||
|
<div class="w-full">
|
||||||
|
<strong>Boot:</strong>
|
||||||
|
{{ reservation.trailer.name }}
|
||||||
|
<br />
|
||||||
|
<strong>Reservierung:</strong>
|
||||||
|
{{ reservation.user_applicant.name }}
|
||||||
|
<br />
|
||||||
|
<strong>Datum:</strong>
|
||||||
|
{{ reservation.start_date }}
|
||||||
|
{% if reservation.end_date != reservation.start_date %}
|
||||||
|
-
|
||||||
|
{{ reservation.end_date }}
|
||||||
|
{% endif %}
|
||||||
|
<br />
|
||||||
|
{% if not allowed_to_edit %}
|
||||||
|
<strong>Uhrzeit:</strong>
|
||||||
|
{{ reservation.time_desc }}
|
||||||
|
<br />
|
||||||
|
<strong>Zweck:</strong>
|
||||||
|
{{ reservation.usage }}
|
||||||
|
{% endif %}
|
||||||
|
{% if allowed_to_edit %}
|
||||||
|
<form action="/trailerreservation"
|
||||||
|
method="post"
|
||||||
|
class="bg-white dark:bg-primary-900 pt-3 rounded-md w-full">
|
||||||
|
<div class="w-full grid gap-3">
|
||||||
|
<input type="hidden" name="id" value="{{ reservation.id }}" />
|
||||||
|
{{ macros::input(label='Uhrzeit', name='time_desc', id=loop.index, type="text", value=reservation.time_desc, readonly=false) }}
|
||||||
|
{{ macros::input(label='Zweck', name='usage', id=loop.index, type="text", value=reservation.usage, readonly=false) }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 text-right">
|
||||||
|
<a href="/trailerreservation/{{ reservation.id }}/delete"
|
||||||
|
class="w-28 btn btn-alert"
|
||||||
|
onclick="return confirm('Willst du diese Reservierung wirklich löschen?');">
|
||||||
|
{% include "includes/delete-icon" %}
|
||||||
|
Löschen
|
||||||
|
</a>
|
||||||
|
<input value="Ändern" type="submit" class="w-28 btn btn-primary ml-1" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user