update-logbook-entries #633

Merged
philipp merged 2 commits from update-logbook-entries into staging 2024-07-23 15:04:49 +02:00
5 changed files with 193 additions and 80 deletions
Showing only changes of commit 4a3ee5b551 - Show all commits

View File

@ -60,6 +60,22 @@ pub struct LogToFinalize {
pub rowers: Vec<i64>, pub rowers: Vec<i64>,
} }
#[derive(FromForm, Debug, Clone)]
pub struct LogToUpdate {
pub id: i64,
pub boat_id: i64,
pub shipmaster: i64,
pub steering_person: i64,
pub shipmaster_only_steering: bool,
pub departure: String,
pub arrival: Option<String>,
pub destination: Option<String>,
pub distance_in_km: Option<i64>,
pub comments: Option<String>,
pub logtype: Option<i64>,
pub rowers: Vec<i64>,
}
impl TryFrom<LogToAdd> for LogToFinalize { impl TryFrom<LogToAdd> for LogToFinalize {
type Error = String; type Error = String;
@ -94,6 +110,11 @@ pub struct LogbookWithBoatAndRowers {
pub rowers: Vec<User>, pub rowers: Vec<User>,
} }
#[derive(Debug, PartialEq)]
pub enum LogbookAdminUpdateError {
NotAllowed,
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum LogbookUpdateError { pub enum LogbookUpdateError {
NotYourEntry, NotYourEntry,
@ -172,7 +193,7 @@ impl Logbook {
.await .await
.ok() .ok()
} }
pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> { pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
sqlx::query_as!( sqlx::query_as!(
Self, Self,
" "
@ -437,6 +458,35 @@ ORDER BY departure DESC
Ok(ret) Ok(ret)
} }
pub async fn update(
&self,
db: &SqlitePool,
data: LogToUpdate,
user: &User,
) -> Result<(), LogbookAdminUpdateError> {
if !user.has_role(db, "Vorstand").await {
return Err(LogbookAdminUpdateError::NotAllowed);
}
sqlx::query!(
"UPDATE logbook SET boat_id=?, shipmaster=?, steering_person=?, shipmaster_only_steering=?, departure=?, arrival=?, destination=?, distance_in_km=?, comments=?, logtype=? WHERE id=?",
data.boat_id,
data.shipmaster,
data.steering_person,
data.shipmaster_only_steering,
data.departure,
data.arrival,
data.destination,
data.distance_in_km,
data.comments,
data.logtype,
self.id
)
.execute(db)
.await.unwrap();
Ok(())
}
pub async fn distances(db: &SqlitePool) -> Vec<(String, i64)> { pub async fn distances(db: &SqlitePool) -> Vec<(String, i64)> {
let result = sqlx::query!("SELECT destination, distance_in_km FROM logbook WHERE id IN (SELECT MIN(id) FROM logbook GROUP BY destination) AND destination IS NOT NULL AND distance_in_km IS NOT NULL;") let result = sqlx::query!("SELECT destination, distance_in_km FROM logbook WHERE id IN (SELECT MIN(id) FROM logbook GROUP BY destination) AND destination IS NOT NULL AND distance_in_km IS NOT NULL;")
.fetch_all(db) .fetch_all(db)

View File

@ -20,11 +20,11 @@ use crate::model::{
boatreservation::BoatReservation, boatreservation::BoatReservation,
log::Log, log::Log,
logbook::{ logbook::{
LogToAdd, LogToFinalize, Logbook, LogbookCreateError, LogbookDeleteError, LogToAdd, LogToFinalize, LogToUpdate, Logbook, LogbookAdminUpdateError, LogbookCreateError,
LogbookUpdateError, LogbookDeleteError, LogbookUpdateError,
}, },
logtype::LogType, logtype::LogType,
user::{AdminUser, DonauLinzUser, User, UserWithDetails}, user::{AdminUser, DonauLinzUser, User, UserWithDetails, VorstandUser},
}; };
pub struct KioskCookie(()); pub struct KioskCookie(());
@ -282,10 +282,40 @@ async fn create_kiosk(
create_logbook(db, data, &DonauLinzUser(creator)).await //TODO: fixme create_logbook(db, data, &DonauLinzUser(creator)).await //TODO: fixme
} }
#[post("/update", data = "<data>")]
async fn update(
db: &State<SqlitePool>,
data: Form<LogToUpdate>,
user: VorstandUser,
) -> Flash<Redirect> {
Log::create(
db,
format!("User {} tries to update log entry={:?}", &user.name, data),
)
.await;
let data = data.into_inner();
let Some(logbook) = Logbook::find_by_id(db, data.id).await else {
return Flash::error(Redirect::to("/log"), &format!("Logbucheintrag kann nicht bearbeitet werden, da es einen Logbuch-Eintrag mit ID={} nicht gibt", data.id));
};
match logbook.update(db, data, &user.0).await {
Ok(()) => Flash::success(
Redirect::to("/log/show"),
format!("Logbucheintrag erfolgreich bearbeitet"),
),
Err(LogbookAdminUpdateError::NotAllowed) => Flash::error(
Redirect::to("/log/show"),
format!("Du hast keine Erlaubnis, diesen Logbucheintrag zu bearbeiten!"),
),
}
}
async fn home_logbook( async fn home_logbook(
db: &SqlitePool, db: &SqlitePool,
data: Form<LogToFinalize>, data: Form<LogToFinalize>,
logbook_id: i32, logbook_id: i64,
user: &DonauLinzUser, user: &DonauLinzUser,
) -> Flash<Redirect> { ) -> Flash<Redirect> {
let logbook: Option<Logbook> = Logbook::find_by_id(db, logbook_id).await; let logbook: Option<Logbook> = Logbook::find_by_id(db, logbook_id).await;
@ -312,7 +342,7 @@ async fn home_logbook(
async fn home_kiosk( async fn home_kiosk(
db: &State<SqlitePool>, db: &State<SqlitePool>,
data: Form<LogToFinalize>, data: Form<LogToFinalize>,
logbook_id: i32, logbook_id: i64,
_kiosk: KioskCookie, _kiosk: KioskCookie,
) -> Flash<Redirect> { ) -> Flash<Redirect> {
let logbook = Logbook::find_by_id(db, logbook_id).await.unwrap(); //TODO: fixme let logbook = Logbook::find_by_id(db, logbook_id).await.unwrap(); //TODO: fixme
@ -340,7 +370,7 @@ async fn home_kiosk(
async fn home( async fn home(
db: &State<SqlitePool>, db: &State<SqlitePool>,
data: Form<LogToFinalize>, data: Form<LogToFinalize>,
logbook_id: i32, logbook_id: i64,
user: DonauLinzUser, user: DonauLinzUser,
) -> Flash<Redirect> { ) -> Flash<Redirect> {
Log::create( Log::create(
@ -356,7 +386,7 @@ async fn home(
} }
#[get("/<logbook_id>/delete", rank = 2)] #[get("/<logbook_id>/delete", rank = 2)]
async fn delete(db: &State<SqlitePool>, logbook_id: i32, user: DonauLinzUser) -> Flash<Redirect> { async fn delete(db: &State<SqlitePool>, logbook_id: i64, user: DonauLinzUser) -> Flash<Redirect> {
let logbook = Logbook::find_by_id(db, logbook_id).await; let logbook = Logbook::find_by_id(db, logbook_id).await;
if let Some(logbook) = logbook { if let Some(logbook) = logbook {
Log::create( Log::create(
@ -385,7 +415,7 @@ async fn delete(db: &State<SqlitePool>, logbook_id: i32, user: DonauLinzUser) ->
#[get("/<logbook_id>/delete")] #[get("/<logbook_id>/delete")]
async fn delete_kiosk( async fn delete_kiosk(
db: &State<SqlitePool>, db: &State<SqlitePool>,
logbook_id: i32, logbook_id: i64,
_kiosk: KioskCookie, _kiosk: KioskCookie,
) -> Flash<Redirect> { ) -> Flash<Redirect> {
let logbook = Logbook::find_by_id(db, logbook_id).await; let logbook = Logbook::find_by_id(db, logbook_id).await;
@ -425,7 +455,8 @@ pub fn routes() -> Vec<Route> {
show_kiosk, show_kiosk,
show_for_year, show_for_year,
delete, delete,
delete_kiosk delete_kiosk,
update
] ]
} }

View File

@ -169,77 +169,101 @@
</div> </div>
</div> </div>
{% endmacro show %} {% endmacro show %}
{% macro show_old(log, state, allowed_to_close=false, index) %} {% macro show_old(log, state, allowed_to_close=false, allowed_to_edit=false, index) %}
<div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative" <div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative"
data-filterable="true" data-filterable="true"
data-filter="{{ log.boat.name }} {% for rower in log.rowers %}{{ rower.name }}{% endfor %}"> data-filter="{{ log.boat.name }} {% for rower in log.rowers %}{{ rower.name }}{% endfor %}">
{% if log.logtype %} <details>
<div class="absolute top-0 right-0 bg-primary-100 rounded-bl-md text-primary-950 text-xs w-32 px-2 py-1 text-center font-bold"> <summary style="list-style: none;">
{% if log.logtype == 1 %} {% if log.logtype %}
Wanderfahrt <div class="absolute top-0 right-0 bg-primary-100 rounded-bl-md text-primary-950 text-xs w-32 px-2 py-1 text-center font-bold">
{% else %} {% if log.logtype == 1 %}
{% if log.logtype == 2 %} Wanderfahrt
Regatta {% else %}
{% else %} {% if log.logtype == 2 %}
{{ log.logtype }} Regatta
{% endif %} {% else %}
{% endif %} {{ log.logtype }}
</div> {% endif %}
{% endif %}
<div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
<strong class="text-black dark:text-white">{{ log.boat.name }}</strong>
<small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
{% if log.shipmaster_only_steering %}
- handgesteuert
{%- endif -%}
)</small>
<small class="block text-gray-600 dark:text-gray-100">
{% if state == "completed" and log.departure | date(format='%d.%m.%Y') == log.arrival | date(format='%d.%m.%Y') %}
{{ log.departure | date(format='%d.%m.%Y') }}
({{ log.departure | date(format='%H:%M') }}
-
{{ log.arrival | date(format='%H:%M') }})
{% else %}
{{ log.departure | date(format='%d.%m.%Y (%H:%M)') }}
{% if state == "completed" %}
-
{{ log.arrival | date(format='%d.%m.%Y (%H:%M)') }}
{% endif %}
{% endif %}
</small>
{% set amount_rowers = log.rowers | length %}
{% set amount_guests = log.boat.amount_seats - amount_rowers %}
{% if allowed_to_close and state == "on_water" %}
{{ log::home(log=log) }}
{% else %}
<div class="text-black dark:text-white">
{{ log.destination }}
{% if state == "completed" %}
<small class="text-gray-600 dark:text-gray-100">({{ log.distance_in_km }}
km)</small>
{% endif %}
{% if log.comments %}<span class="text-sm italic">- "{{ log.comments }}"</span>{% endif %}
</div>
{% if amount_guests > 0 or log.rowers | length > 0 %}
{% if not log.boat.amount_seats == 1 %}
<div class="text-sm text-gray-600 dark:text-gray-100">
Ruderer:
{% for rower in log.rowers -%}
{{ rower.name }}
{%- if rower.id == log.steering_user.id and rower.id != log.shipmaster_user.id %}
(Steuerperson){%- endif -%}
{%- if not loop.last or amount_guests > 0 and not log.boat.external %},{% endif %}
{% endfor -%}
{% if amount_guests > 0 and not log.boat.external %}
Gäste
<small class="text-gray-600 dark:text-gray-100">(ohne Account)</small>:
{{ amount_guests }}
{% endif %}
</div>
{% endif %} {% endif %}
{% endif %} </div>
{% endif %} {% endif %}
</div> <div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
<strong class="text-black dark:text-white">{{ log.boat.name }}</strong>
<small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
{% if log.shipmaster_only_steering %}
- handgesteuert
{%- endif -%}
)</small>
<small class="block text-gray-600 dark:text-gray-100">
{% if state == "completed" and log.departure | date(format='%d.%m.%Y') == log.arrival | date(format='%d.%m.%Y') %}
{{ log.departure | date(format='%d.%m.%Y') }}
({{ log.departure | date(format='%H:%M') }}
-
{{ log.arrival | date(format='%H:%M') }})
{% else %}
{{ log.departure | date(format='%d.%m.%Y (%H:%M)') }}
{% if state == "completed" %}
-
{{ log.arrival | date(format='%d.%m.%Y (%H:%M)') }}
{% endif %}
{% endif %}
</small>
{% set amount_rowers = log.rowers | length %}
{% set amount_guests = log.boat.amount_seats - amount_rowers %}
{% if allowed_to_close and state == "on_water" %}
{{ log::home(log=log) }}
{% else %}
<div class="text-black dark:text-white">
{{ log.destination }}
{% if state == "completed" %}
<small class="text-gray-600 dark:text-gray-100">({{ log.distance_in_km }}
km)</small>
{% endif %}
{% if log.comments %}<span class="text-sm italic">- "{{ log.comments }}"</span>{% endif %}
</div>
{% if amount_guests > 0 or log.rowers | length > 0 %}
{% if not log.boat.amount_seats == 1 %}
<div class="text-sm text-gray-600 dark:text-gray-100">
Ruderer:
{% for rower in log.rowers -%}
{{ rower.name }}
{%- if rower.id == log.steering_user.id and rower.id != log.shipmaster_user.id %}
(Steuerperson){%- endif -%}
{%- if not loop.last or amount_guests > 0 and not log.boat.external %},{% endif %}
{% endfor -%}
{% if amount_guests > 0 and not log.boat.external %}
Gäste
<small class="text-gray-600 dark:text-gray-100">(ohne Account)</small>:
{{ amount_guests }}
{% endif %}
</div>
{% endif %}
{% endif %}
{% endif %}
</div>
</summary>
{% if allowed_to_edit %}
<form action="/log/update" method="post">
<input type="hidden" name="id" value="{{ log.id }}" />
<input type="hidden" name="boat_id" value="{{ log.boat_id }}" />
<input type="hidden" name="shipmaster" value="{{ log.shipmaster }}" />
<input type="hidden"
name="steering_person"
value="{{ log.steering_person }}" />
<input type="hidden"
name="shipmaster_only_steering"
value="{{ log.shipmaster_only_steering }}" />
<input type="datetime-local" name="departure" value="{{ log.departure }}" />
<input type="datetime-local" name="arrival" value="{{ log.arrival }}" />
<input type="hidden" name="destination" value="{{ log.destination }}" />
<input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" />
<input type="hidden" name="comments" value="{{ log.comments }}" />
<input type="hidden" name="logtype" value="{{ log.logtype }}" />
<input type="submit" value="Updaten" />
</form>
{% endif %}
</details>
</div> </div>
{% endmacro show_old %} {% endmacro show_old %}
{% macro home(log) %} {% macro home(log) %}

View File

@ -143,9 +143,9 @@
<input type="checkbox" <input type="checkbox"
id="{{ name }}{{ id }}" id="{{ name }}{{ id }}"
name="{{ name }}" name="{{ name }}"
{% if checked %} checked{% endif %} {% if checked %}checked{% endif %}
{% if disabled %} disabled{% endif %} {% if disabled %}disabled{% endif %}
{% if readonly %} readonly="readonly"{% endif %} {% if readonly %}readonly="readonly"{% endif %}
class="h-4 w-4 accent-primary-600 dark:accent-primary-200 mr-2" /> class="h-4 w-4 accent-primary-600 dark:accent-primary-200 mr-2" />
{{ label }} {{ label }}
</label> </label>

View File

@ -23,7 +23,15 @@
placeholder="Suchen nach Bootsname oder Ruderer..."> placeholder="Suchen nach Bootsname oder Ruderer...">
</div> </div>
<div id="filter-result-js" class="search-result"></div> <div id="filter-result-js" class="search-result"></div>
{% for log in logs %}{{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index) }}{% endfor %} {% for log in logs %}
{% set_global allowed_to_edit = false %}
{% if loggedin_user %}
{% if "Vorstand" in loggedin_user.roles %}
{% set_global allowed_to_edit = true %}
{% endif %}
{% endif %}
{{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index, allowed_to_edit=allowed_to_edit) }}
{% endfor %}
</div> </div>
</div> </div>
<script> <script>