Compare commits
23 Commits
df1a06531f
...
test
Author | SHA1 | Date | |
---|---|---|---|
2159696112 | |||
39bde35864 | |||
5f301324ee | |||
9558965e8f | |||
5f4d8982a8 | |||
0e2ef9e256 | |||
1dc91f4f28 | |||
f56da43723 | |||
9dc1ec6fa0 | |||
c9b67f5790 | |||
16687e39ab | |||
957c474389 | |||
f7aed68423 | |||
01637d0800 | |||
0a77011170 | |||
dea0c65da3 | |||
31bf38f112 | |||
6b29907596 | |||
7b17c30ce2 | |||
ec4068e499 | |||
fca19745f8 | |||
bb48ddb3de | |||
34b098fa2a |
@ -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 trailer(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');
|
||||||
|
@ -81,20 +81,20 @@ pub struct BoatToUpdate<'r> {
|
|||||||
|
|
||||||
impl Boat {
|
impl Boat {
|
||||||
pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
|
pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
|
||||||
sqlx::query_as!(Self, "SELECT * FROM boat WHERE id like ?", id)
|
sqlx::query_as!(Self, "SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, convert_handoperated_possible, default_destination, skull, external, deleted FROM boat WHERE id like ?", id)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, id: i32) -> Option<Self> {
|
pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, id: i32) -> Option<Self> {
|
||||||
sqlx::query_as!(Self, "SELECT * FROM boat WHERE id like ?", id)
|
sqlx::query_as!(Self, "SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, convert_handoperated_possible, default_destination, skull, external, deleted FROM boat WHERE id like ?", id)
|
||||||
.fetch_one(db.deref_mut())
|
.fetch_one(db.deref_mut())
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_name(db: &SqlitePool, name: String) -> Option<Self> {
|
pub async fn find_by_name(db: &SqlitePool, name: String) -> Option<Self> {
|
||||||
sqlx::query_as!(Self, "SELECT * FROM boat WHERE name like ?", name)
|
sqlx::query_as!(Self, "SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, convert_handoperated_possible, default_destination, skull, external, deleted FROM boat WHERE name like ?", name)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -360,21 +360,23 @@ WHERE trip_details.id=?
|
|||||||
event.day.replace('-', ""),
|
event.day.replace('-', ""),
|
||||||
event.planned_starting_time.replace(':', "")
|
event.planned_starting_time.replace(':', "")
|
||||||
)));
|
)));
|
||||||
|
let tripdetails = event.trip_details(db).await;
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
if event.is_cancelled() {
|
if event.is_cancelled() {
|
||||||
name.push_str("ABGESAGT! :-( ");
|
name.push_str("ABGESAGT");
|
||||||
|
if let Some(notes) = &tripdetails.notes {
|
||||||
|
if !notes.is_empty() {
|
||||||
|
name.push_str(&format!(" (Grund: {notes})"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name.push_str("! :-( ");
|
||||||
}
|
}
|
||||||
name.push_str(&format!("{} ", event.name));
|
name.push_str(&format!("{} ", event.name));
|
||||||
|
|
||||||
let tripdetails = event.trip_details(db).await;
|
|
||||||
if let Some(triptype) = tripdetails.triptype(db).await {
|
if let Some(triptype) = tripdetails.triptype(db).await {
|
||||||
name.push_str(&format!("• {} ", triptype.name))
|
name.push_str(&format!("• {} ", triptype.name))
|
||||||
}
|
}
|
||||||
if let Some(notes) = tripdetails.notes {
|
|
||||||
if !notes.is_empty() {
|
|
||||||
name.push_str(&format!("({notes}) "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vevent.push(Summary::new(name));
|
vevent.push(Summary::new(name));
|
||||||
calendar.add_event(vevent);
|
calendar.add_event(vevent);
|
||||||
}
|
}
|
||||||
@ -434,6 +436,6 @@ mod test {
|
|||||||
let pool = testdb!();
|
let pool = testdb!();
|
||||||
|
|
||||||
let actual = Event::get_ics_feed(&pool).await;
|
let actual = Event::get_ics_feed(&pool).await;
|
||||||
assert_eq!("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-rs\r\nBEGIN:VEVENT\r\nUID:1@rudernlinz.at\r\nDTSTAMP:19900101T180000\r\nDTSTART:19700101T100000\r\nSUMMARY:test-planned-event (trip_details for a planned event) \r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", actual);
|
assert_eq!("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-rs\r\nBEGIN:VEVENT\r\nUID:1@rudernlinz.at\r\nDTSTAMP:19900101T180000\r\nDTSTART:19700101T100000\r\nSUMMARY:test-planned-event \r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,7 +301,7 @@ ORDER BY departure DESC
|
|||||||
db: &SqlitePool,
|
db: &SqlitePool,
|
||||||
mut log: LogToAdd,
|
mut log: LogToAdd,
|
||||||
created_by_user: &User,
|
created_by_user: &User,
|
||||||
) -> Result<(), LogbookCreateError> {
|
) -> Result<String, LogbookCreateError> {
|
||||||
let Some(boat) = Boat::find_by_id(db, log.boat_id).await else {
|
let Some(boat) = Boat::find_by_id(db, log.boat_id).await else {
|
||||||
return Err(LogbookCreateError::BoatNotFound);
|
return Err(LogbookCreateError::BoatNotFound);
|
||||||
};
|
};
|
||||||
@ -354,7 +354,7 @@ ORDER BY departure DESC
|
|||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tx.commit().await.unwrap();
|
tx.commit().await.unwrap();
|
||||||
Ok(())
|
Ok(String::new())
|
||||||
}
|
}
|
||||||
Err(a) => Err(a.into()),
|
Err(a) => Err(a.into()),
|
||||||
};
|
};
|
||||||
@ -426,7 +426,15 @@ ORDER BY departure DESC
|
|||||||
|
|
||||||
tx.commit().await.unwrap();
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
Ok(())
|
let mut ret = String::new();
|
||||||
|
for rower in &log.rowers {
|
||||||
|
let user = User::find_by_id(db, *rower as i32).await.unwrap();
|
||||||
|
if let Some(msg) = user.close_thousands_trip(db).await {
|
||||||
|
ret.push_str(&format!(" • {msg}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn distances(db: &SqlitePool) -> Vec<(String, i64)> {
|
pub async fn distances(db: &SqlitePool) -> Vec<(String, i64)> {
|
||||||
@ -635,6 +643,7 @@ mod test {
|
|||||||
use crate::model::user::User;
|
use crate::model::user::User;
|
||||||
use crate::testdb;
|
use crate::testdb;
|
||||||
|
|
||||||
|
use chrono::Duration;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
#[sqlx::test]
|
#[sqlx::test]
|
||||||
@ -686,7 +695,7 @@ mod test {
|
|||||||
fn test_succ_create() {
|
fn test_succ_create() {
|
||||||
let pool = testdb!();
|
let pool = testdb!();
|
||||||
|
|
||||||
Logbook::create(
|
let msg = Logbook::create(
|
||||||
&pool,
|
&pool,
|
||||||
LogToAdd {
|
LogToAdd {
|
||||||
boat_id: 3,
|
boat_id: 3,
|
||||||
@ -704,7 +713,62 @@ mod test {
|
|||||||
&User::find_by_id(&pool, 4).await.unwrap(),
|
&User::find_by_id(&pool, 4).await.unwrap(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap();
|
||||||
|
assert_eq!(msg, String::from(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
fn test_succ_create_with_thousands_msg() {
|
||||||
|
let pool = testdb!();
|
||||||
|
|
||||||
|
let logbook = Logbook::find_by_id(&pool, 1).await.unwrap();
|
||||||
|
let user = User::find_by_id(&pool, 2).await.unwrap();
|
||||||
|
let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();
|
||||||
|
let start_date = chrono::Local::now() - Duration::days(3);
|
||||||
|
let start_date = start_date.format("%Y-%m-%d").to_string();
|
||||||
|
logbook
|
||||||
|
.home(
|
||||||
|
&pool,
|
||||||
|
&user,
|
||||||
|
super::LogToFinalize {
|
||||||
|
destination: "new-destination".into(),
|
||||||
|
distance_in_km: 995,
|
||||||
|
comments: Some("Perfect water".into()),
|
||||||
|
logtype: None,
|
||||||
|
rowers: vec![2],
|
||||||
|
shipmaster: Some(2),
|
||||||
|
steering_person: Some(2),
|
||||||
|
shipmaster_only_steering: false,
|
||||||
|
departure: format!("{}T10:00", start_date),
|
||||||
|
arrival: format!("{}T12:00", current_date),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let msg = Logbook::create(
|
||||||
|
&pool,
|
||||||
|
LogToAdd {
|
||||||
|
boat_id: 3,
|
||||||
|
shipmaster: Some(2),
|
||||||
|
steering_person: Some(2),
|
||||||
|
shipmaster_only_steering: false,
|
||||||
|
departure: "2128-05-20T12:00".into(),
|
||||||
|
arrival: None,
|
||||||
|
destination: None,
|
||||||
|
distance_in_km: None,
|
||||||
|
comments: None,
|
||||||
|
logtype: None,
|
||||||
|
rowers: vec![2],
|
||||||
|
},
|
||||||
|
&User::find_by_id(&pool, 1).await.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
msg,
|
||||||
|
String::from(" • rower braucht nur mehr 5 km bis die 1000 km voll sind 🤑")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[sqlx::test]
|
#[sqlx::test]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
use waterlevel::WaterlevelDay;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
event::{Event, EventWithUserAndTriptype},
|
event::{Event, EventWithUserAndTriptype},
|
||||||
@ -24,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;
|
||||||
@ -38,7 +41,7 @@ pub struct Day {
|
|||||||
events: Vec<EventWithUserAndTriptype>,
|
events: Vec<EventWithUserAndTriptype>,
|
||||||
trips: Vec<TripWithUserAndType>,
|
trips: Vec<TripWithUserAndType>,
|
||||||
is_pinned: bool,
|
is_pinned: bool,
|
||||||
max_waterlevel: Option<i64>,
|
max_waterlevel: Option<WaterlevelDay>,
|
||||||
weather: Option<Weather>,
|
weather: Option<Weather>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ ORDER BY
|
|||||||
#[derive(FromRow, Serialize, Clone)]
|
#[derive(FromRow, Serialize, Clone)]
|
||||||
pub struct Stat {
|
pub struct Stat {
|
||||||
name: String,
|
name: String,
|
||||||
rowed_km: i32,
|
pub(crate) rowed_km: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stat {
|
impl Stat {
|
||||||
@ -195,6 +195,34 @@ ORDER BY rowed_km DESC, u.name;
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
pub async fn person(db: &SqlitePool, year: Option<i32>, user: &User) -> Stat {
|
||||||
|
let year = match year {
|
||||||
|
Some(year) => year,
|
||||||
|
None => chrono::Local::now().year(),
|
||||||
|
};
|
||||||
|
//TODO: switch to query! macro again (once upgraded to sqlite 3.42 on server)
|
||||||
|
let row = sqlx::query(&format!(
|
||||||
|
"
|
||||||
|
SELECT u.name, CAST(SUM(l.distance_in_km) AS INTEGER) AS rowed_km
|
||||||
|
FROM (
|
||||||
|
SELECT * FROM user
|
||||||
|
WHERE id={}
|
||||||
|
) u
|
||||||
|
INNER JOIN rower r ON u.id = r.rower_id
|
||||||
|
INNER JOIN logbook l ON r.logbook_id = l.id
|
||||||
|
WHERE l.distance_in_km IS NOT NULL AND l.arrival LIKE '{year}-%';
|
||||||
|
",
|
||||||
|
user.id
|
||||||
|
))
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Stat {
|
||||||
|
name: row.get("name"),
|
||||||
|
rowed_km: row.get("rowed_km"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@ -218,7 +246,7 @@ FROM (
|
|||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
rower r ON l.id = r.logbook_id
|
rower r ON l.id = r.logbook_id
|
||||||
WHERE
|
WHERE
|
||||||
l.shipmaster = {0} OR r.rower_id = {0}
|
r.rower_id = {}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
departure_date
|
departure_date
|
||||||
) as subquery
|
) as subquery
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
family::Family, log::Log, mail::Mail, notification::Notification, role::Role,
|
family::Family, log::Log, mail::Mail, notification::Notification, role::Role, stat::Stat,
|
||||||
tripdetails::TripDetails, Day,
|
tripdetails::TripDetails, Day,
|
||||||
};
|
};
|
||||||
use crate::tera::admin::user::UserEditForm;
|
use crate::tera::admin::user::UserEditForm;
|
||||||
@ -281,6 +281,8 @@ Für die Organisation unserer Ausfahrten nutzen wir app.rudernlinz.at. Logge dic
|
|||||||
|
|
||||||
Beim nächsten Treffen im Verein, erinnere mich (Philipp Hofer) bitte daran, deinen Fingerabdruck zu registrieren, damit du eigenständig Zugang zum Bootshaus erhältst.
|
Beim nächsten Treffen im Verein, erinnere mich (Philipp Hofer) bitte daran, deinen Fingerabdruck zu registrieren, damit du eigenständig Zugang zum Bootshaus erhältst.
|
||||||
|
|
||||||
|
Außerdem haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz'. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter.
|
||||||
|
|
||||||
Wir freuen uns darauf, dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln!
|
Wir freuen uns darauf, dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln!
|
||||||
|
|
||||||
Riemen- & Dollenbruch
|
Riemen- & Dollenbruch
|
||||||
@ -849,6 +851,19 @@ ORDER BY last_access DESC
|
|||||||
6
|
6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn close_thousands_trip(&self, db: &SqlitePool) -> Option<String> {
|
||||||
|
let rowed_km = Stat::person(db, None, self).await.rowed_km;
|
||||||
|
if rowed_km % 1000 > 970 {
|
||||||
|
return Some(format!(
|
||||||
|
"{} braucht nur mehr {} km bis die {} km voll sind 🤑",
|
||||||
|
self.name,
|
||||||
|
1000 - rowed_km % 1000,
|
||||||
|
rowed_km + 1000 - (rowed_km % 1000)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -17,6 +17,13 @@ pub struct Waterlevel {
|
|||||||
pub tumittel: i64,
|
pub tumittel: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct WaterlevelDay {
|
||||||
|
pub day: NaiveDate,
|
||||||
|
pub avg: i64,
|
||||||
|
pub fluctuation: i64,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Create {
|
pub struct Create {
|
||||||
pub day: NaiveDate,
|
pub day: NaiveDate,
|
||||||
pub time: String,
|
pub time: String,
|
||||||
@ -53,15 +60,28 @@ impl Waterlevel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn max_waterlevel_for_day(db: &SqlitePool, day: NaiveDate) -> Option<i64> {
|
pub async fn max_waterlevel_for_day(db: &SqlitePool, day: NaiveDate) -> Option<WaterlevelDay> {
|
||||||
sqlx::query!(
|
let waterlevel = sqlx::query_as!(
|
||||||
"SELECT MAX(mittel) as max FROM waterlevel WHERE day = ?",
|
Waterlevel,
|
||||||
|
"SELECT id, day, time, max, min, mittel, tumax, tumin, tumittel FROM waterlevel WHERE day = ? ORDER BY mittel DESC LIMIT 1",
|
||||||
day
|
day
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_optional(db)
|
||||||
.await
|
.await.unwrap();
|
||||||
.unwrap()
|
|
||||||
.max
|
if let Some(waterlevel) = waterlevel {
|
||||||
|
let max_diff = (waterlevel.mittel - waterlevel.max).abs();
|
||||||
|
let min_diff = (waterlevel.mittel - waterlevel.min).abs();
|
||||||
|
let fluctuation = max_diff.max(min_diff);
|
||||||
|
|
||||||
|
return Some(WaterlevelDay {
|
||||||
|
day: waterlevel.day,
|
||||||
|
avg: waterlevel.mittel,
|
||||||
|
fluctuation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_all(db: &mut Transaction<'_, Sqlite>) {
|
pub async fn delete_all(db: &mut Transaction<'_, Sqlite>) {
|
||||||
|
@ -212,7 +212,7 @@ async fn create_logbook(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt erfolgreich hinzugefügt"),
|
Ok(msg) => Flash::success(Redirect::to("/log"), format!("Ausfahrt erfolgreich hinzugefügt{msg}")),
|
||||||
Err(LogbookCreateError::BoatAlreadyOnWater) => Flash::error(Redirect::to("/log"), "Boot schon am Wasser"),
|
Err(LogbookCreateError::BoatAlreadyOnWater) => Flash::error(Redirect::to("/log"), "Boot schon am Wasser"),
|
||||||
Err(LogbookCreateError::RowerAlreadyOnWater(rower)) => Flash::error(Redirect::to("/log"), format!("Ruderer {} schon am Wasser", rower.name)),
|
Err(LogbookCreateError::RowerAlreadyOnWater(rower)) => Flash::error(Redirect::to("/log"), format!("Ruderer {} schon am Wasser", rower.name)),
|
||||||
Err(LogbookCreateError::BoatLocked) => Flash::error(Redirect::to("/log"),"Boot gesperrt"),
|
Err(LogbookCreateError::BoatLocked) => Flash::error(Redirect::to("/log"),"Boot gesperrt"),
|
||||||
|
@ -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 %}
|
||||||
|
@ -100,7 +100,10 @@
|
|||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Die Wetterdaten werden von <a class="underline" href="https://openweathermap.org">OpenWeather</a> bereitgestellt.
|
Die <strong>Wetterdaten</strong> werden von <a class="underline" href="https://openweathermap.org">OpenWeather</a> bereitgestellt.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Wasserstandsvorhersagen:</strong> Die Vorhersagen werden stündlich vom <a class="underline" href="https://hydro.ooe.gv.at">Hydrographischen Dienstes Oberösterreich</a> geladen und zwischengespeichert, der höchste Tages-Mittelwert wird gemeinsam mit der Schwankungsbreite bei den geplanten Ausfahrten angezeigt. Es handelt sich hierbei um ungeprüfte Rohdaten. Rohdatenfehler können durch betriebliche Störungen an den Messgeräten, Fernübertragungseinrichtungen u. dgl. entstehen. Die Vorhersagen sind daher mit Unsicherheiten behaftet! Mit der Länge des Vorhersagezeitraumeszeitraumes werden diese Unsicherheiten größer! Es wird keine Gewähr für die Vollständigkeit, Richtigkeit und Genauigkeit der dargestellten Daten übernommen. Gewährleistungs- und Haftungsansprüche werden ausdrücklich ausgeschlossen (sowohl vom Hydrographischen Dienstes Oberösterreich als auch vom ASKÖ Ruderverein Donau Linz).
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endmacro new %}
|
{% endmacro new %}
|
||||||
{% macro boat_select(id="boat_id") %}
|
{% macro boat_select(id="boat_id") %}
|
||||||
{{ macros::select(label="Boot", data=boats, name="boat_id", id=id, display=["name", " (","cat",")"], extras=["default_shipmaster_only_steering", "amount_seats", "on_water", "default_destination"], wrapper_class="col-span-4", show_seats=true, nonSelectableDefault=" -- Wähle ein Boot aus ---") }}
|
{{ macros::select(label="Boot", data=boats, name="boat_id", required=true, id=id, display=["name", " (","cat",")"], extras=["default_shipmaster_only_steering", "amount_seats", "on_water", "default_destination"], wrapper_class="col-span-4", show_seats=true, nonSelectableDefault=" -- Wähle ein Boot aus ---") }}
|
||||||
{% endmacro boat_select %}
|
{% endmacro boat_select %}
|
||||||
{% macro rower_select(id, selected, amount_seats='', class='', init='false', cox_on_boat='', steering_person_id='') %}
|
{% macro rower_select(id, selected, amount_seats='', class='', init='false', cox_on_boat='', steering_person_id='') %}
|
||||||
{#{% if not amount_seats or amount_seats > 1 %}#}
|
{#{% if not amount_seats or amount_seats > 1 %}#}
|
||||||
@ -279,4 +279,8 @@
|
|||||||
</details>
|
</details>
|
||||||
<input class="btn btn-primary" type="submit" value="Ausfahrt beenden" />
|
<input class="btn btn-primary" type="submit" value="Ausfahrt beenden" />
|
||||||
</form>
|
</form>
|
||||||
|
<a href="/log/{{ log.id }}/delete"
|
||||||
|
class="btn btn-alert w-full absolute bottom-0 left-0"
|
||||||
|
style="border-radius: 0"
|
||||||
|
onclick="return confirm('Willst du diesen Eintrag wirklich löschen? Die Daten gehen verloren');">Löschen</a>
|
||||||
{% endmacro home %}
|
{% endmacro home %}
|
||||||
|
@ -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>
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
{% if day.max_waterlevel %}
|
{% if day.max_waterlevel %}
|
||||||
• <a href="https://hydro.ooe.gv.at/#/overview/Wasserstand/station/16668/Linz/Wasserstand"
|
• <a href="https://hydro.ooe.gv.at/#/overview/Wasserstand/station/16668/Linz/Wasserstand"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
title="Prognostizierter maximaler Wasserstand am {{ day.day | date(format="%A", locale="de_AT") }}: {{ day.max_waterlevel }} cm">🌊{{ day.max_waterlevel }} cm</a>
|
title="Prognostizierter maximaler Wasserstand am {{ day.day | date(format="%A", locale="de_AT") }}: {{ day.max_waterlevel.avg }} ± {{ day.max_waterlevel.fluctuation }} cm (ungeprüfte Rohdaten, für Details siehe die Infos dazu im Impressum)">🌊{{ day.max_waterlevel.avg }} ± {{ day.max_waterlevel.fluctuation }} cm</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</small>
|
</small>
|
||||||
{% if day.weather %}
|
{% if day.weather %}
|
||||||
@ -197,8 +197,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{# --- END List Rowers --- #}
|
{# --- END List Rowers --- #}
|
||||||
{% if "manage_events" in loggedin_user.roles %}
|
{% if "manage_events" in loggedin_user.roles %}
|
||||||
<form action="/planned/join/{{ event.trip_details_id }}"
|
<form action="/planned/join/{{ event.trip_details_id }}" method="get" />
|
||||||
method="get" />
|
|
||||||
{{ macros::input(label='Gast', class="input rounded-t", name='user_note', type='text', required=true) }}
|
{{ macros::input(label='Gast', class="input rounded-t", name='user_note', type='text', required=true) }}
|
||||||
<input value="Gast hinzufügen"
|
<input value="Gast hinzufügen"
|
||||||
class="btn btn-primary w-full rounded-t-none-important"
|
class="btn btn-primary w-full rounded-t-none-important"
|
||||||
@ -220,7 +219,7 @@
|
|||||||
{{ macros::input(label='Anzahl Steuerleute', name='planned_amount_cox', type='number', value=event.planned_amount_cox, required=true, min='0') }}
|
{{ macros::input(label='Anzahl Steuerleute', name='planned_amount_cox', type='number', value=event.planned_amount_cox, required=true, min='0') }}
|
||||||
{{ macros::checkbox(label='Immer anzeigen', name='always_show', id=event.id,checked=event.always_show) }}
|
{{ macros::checkbox(label='Immer anzeigen', name='always_show', id=event.id,checked=event.always_show) }}
|
||||||
{{ macros::checkbox(label='Gesperrt', name='is_locked', id=event.id,checked=event.is_locked) }}
|
{{ macros::checkbox(label='Gesperrt', name='is_locked', id=event.id,checked=event.is_locked) }}
|
||||||
{{ macros::select(label='Typ', name='trip_type', data=trip_types, default='Reguläre Ausfahrt', selected_id=event.trip_type_id) }}
|
{{ macros::select(label='Typ', name='trip_type', data=trip_types, default='Reguläre Ausfahrt', selected_id=event.trip_type_id) }}
|
||||||
{{ macros::input(label='Anmerkungen', name='notes', type='input', value=event.notes) }}
|
{{ macros::input(label='Anmerkungen', name='notes', type='input', value=event.notes) }}
|
||||||
<input value="Speichern" class="btn btn-primary" type="submit" />
|
<input value="Speichern" class="btn btn-primary" type="submit" />
|
||||||
</form>
|
</form>
|
||||||
@ -244,7 +243,7 @@
|
|||||||
<form action="/admin/planned-event" method="post" class="grid">
|
<form action="/admin/planned-event" method="post" class="grid">
|
||||||
<input type="hidden" name="_method" value="put" />
|
<input type="hidden" name="_method" value="put" />
|
||||||
<input type="hidden" name="id" value="{{ event.id }}" />
|
<input type="hidden" name="id" value="{{ event.id }}" />
|
||||||
{{ macros::input(label='Grund der Absage', name='notes', type='input', value='') }}
|
{{ macros::input(label='Grund der Absage', name='notes', type='input', value='') }}
|
||||||
{{ macros::input(label='', name='max_people', type='hidden', value=0) }}
|
{{ macros::input(label='', name='max_people', type='hidden', value=0) }}
|
||||||
{{ macros::input(label='', name='name', type='hidden', value=event.name) }}
|
{{ macros::input(label='', name='name', type='hidden', value=event.name) }}
|
||||||
{{ macros::input(label='', name='max_people', type='hidden', value=event.max_people) }}
|
{{ macros::input(label='', name='max_people', type='hidden', value=event.max_people) }}
|
||||||
|
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 ---", required=true) }}
|
||||||
|
{% 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 %}
|
Reference in New Issue
Block a user