Merge pull request 'show steering person in logs if not cox' (#375) from show-steering-person-in-logs into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 12m46s
CI/CD Pipeline / deploy-staging (push) Successful in 5m28s
CI/CD Pipeline / deploy-main (push) Has been skipped

Reviewed-on: #375
This commit is contained in:
philipp 2024-04-15 22:03:39 +02:00
commit 5965b1d626
6 changed files with 124 additions and 57 deletions

View File

@ -81,7 +81,7 @@ test("Cox can start and finish trip", async ({ page }, testInfo) => {
const currentValue = await page.$eval(datetimeSelector, el => el.value); const currentValue = await page.$eval(datetimeSelector, el => el.value);
const currentDate = new Date(currentValue); const currentDate = new Date(currentValue);
currentDate.setMinutes(currentDate.getMinutes() + 1); currentDate.setMinutes(currentDate.getMinutes() + 1);
currentDate.setHours(currentDate.getHours() + 1); currentDate.setHours(currentDate.getHours() - new Date().getTimezoneOffset()/60);
const newDatetime = currentDate.toISOString().slice(0, 16); const newDatetime = currentDate.toISOString().slice(0, 16);
await page.$eval(datetimeSelector, (el, value) => el.value = value, newDatetime); await page.$eval(datetimeSelector, (el, value) => el.value = value, newDatetime);
@ -165,7 +165,7 @@ test("Kiosk can start and finish trip", async ({ page }, testInfo) => {
const currentValue = await page.$eval(datetimeSelector, el => el.value); const currentValue = await page.$eval(datetimeSelector, el => el.value);
const currentDate = new Date(currentValue); const currentDate = new Date(currentValue);
currentDate.setMinutes(currentDate.getMinutes() + 1); currentDate.setMinutes(currentDate.getMinutes() + 1);
currentDate.setHours(currentDate.getHours() + 1); currentDate.setHours(currentDate.getHours() - new Date().getTimezoneOffset()/60);
const newDatetime = currentDate.toISOString().slice(0, 16); const newDatetime = currentDate.toISOString().slice(0, 16);
await page.$eval(datetimeSelector, (el, value) => el.value = value, newDatetime); await page.$eval(datetimeSelector, (el, value) => el.value = value, newDatetime);

View File

@ -5,7 +5,9 @@ use rocket::FromForm;
use serde::Serialize; use serde::Serialize;
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::{boat::Boat, log::Log, notification::Notification, rower::Rower, user::User}; use super::{
boat::Boat, log::Log, notification::Notification, role::Role, rower::Rower, user::User,
};
#[derive(FromRow, Serialize, Clone, Debug)] #[derive(FromRow, Serialize, Clone, Debug)]
pub struct Logbook { pub struct Logbook {
@ -505,7 +507,7 @@ ORDER BY departure DESC
let dep = NaiveDateTime::parse_from_str(&log.departure, "%Y-%m-%dT%H:%M").unwrap(); let dep = NaiveDateTime::parse_from_str(&log.departure, "%Y-%m-%dT%H:%M").unwrap();
let arr = NaiveDateTime::parse_from_str(&log.arrival, "%Y-%m-%dT%H:%M").unwrap(); let arr = NaiveDateTime::parse_from_str(&log.arrival, "%Y-%m-%dT%H:%M").unwrap();
if arr.timestamp() <= dep.timestamp() { if arr.timestamp() < dep.timestamp() {
return Err(LogbookUpdateError::ArrivalNotAfterDeparture); return Err(LogbookUpdateError::ArrivalNotAfterDeparture);
} }
let today = Local::now().date_naive(); let today = Local::now().date_naive();
@ -560,6 +562,31 @@ ORDER BY departure DESC
.execute(db.deref_mut()) .execute(db.deref_mut())
.await.unwrap(); //TODO: fixme .await.unwrap(); //TODO: fixme
let duration = arr - dep;
if duration.num_days() > 0 {
let vorstand = Role::find_by_name_tx(db, "Vorstand").await.unwrap();
Notification::create_for_role_tx(
db,
&vorstand,
&format!("'{}' hat eine mehrtägige Ausfahrt vom {} bis {} eingetragen ({} km; Ziel: {}; Anmerkungen: {}). Falls das nicht stimmen sollte, bitte nachhaken.",user.name,log.departure, log.arrival, log.distance_in_km, log.destination, log.comments.clone().unwrap_or("".into())),
"Mehrtägige Ausfahrt eingetragen",
None,
).await;
}
if boat.name == "Externes Boot" {
let vorstand = Role::find_by_name_tx(db, "Vorstand").await.unwrap();
Notification::create_for_role_tx(
db,
&vorstand,
&format!("'{}' hat eine Ausfahrt mit *Externem Boot* am {} eingetragen ({} km; Ziel: {}; Anmerkungen: {}). Falls das nicht stimmen sollte, bitte nachhaken.",user.name,log.departure,log.distance_in_km, log.destination, log.comments.unwrap_or("".into())),
"Ausfahrt mit externem Boot eingetragen",
None,
).await;
}
Ok(()) Ok(())
} }

View File

@ -4,7 +4,7 @@ use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::user::User; use super::{role::Role, user::User};
#[derive(FromRow, Debug, Serialize, Deserialize)] #[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct Notification { pub struct Notification {
@ -55,6 +55,20 @@ impl Notification {
tx.commit().await.unwrap(); tx.commit().await.unwrap();
} }
pub async fn create_for_role_tx(
db: &mut Transaction<'_, Sqlite>,
role: &Role,
message: &str,
category: &str,
link: Option<&str>,
) {
let users = User::all_with_role_tx(db, role).await;
for user in users {
Self::create_with_tx(db, &user, message, category, link).await;
}
}
pub async fn for_user(db: &SqlitePool, user: &User) -> Vec<Self> { pub async fn for_user(db: &SqlitePool, user: &User) -> Vec<Self> {
sqlx::query_as!( sqlx::query_as!(
Self, Self,

View File

@ -1,5 +1,7 @@
use std::ops::DerefMut;
use serde::Serialize; use serde::Serialize;
use sqlx::{FromRow, SqlitePool}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
#[derive(FromRow, Serialize, Clone)] #[derive(FromRow, Serialize, Clone)]
pub struct Role { pub struct Role {
@ -45,6 +47,21 @@ WHERE name like ?
.ok() .ok()
} }
pub async fn find_by_name_tx(db: &mut Transaction<'_, Sqlite>, name: &str) -> Option<Self> {
sqlx::query_as!(
Self,
"
SELECT id, name
FROM role
WHERE name like ?
",
name
)
.fetch_one(db.deref_mut())
.await
.ok()
}
pub async fn names_from_role(&self, db: &SqlitePool) -> Vec<String> { pub async fn names_from_role(&self, db: &SqlitePool) -> Vec<String> {
let query = format!( let query = format!(
"SELECT u.name "SELECT u.name

View File

@ -370,6 +370,13 @@ ORDER BY last_access DESC
} }
pub async fn all_with_role(db: &SqlitePool, role: &Role) -> Vec<Self> { pub async fn all_with_role(db: &SqlitePool, role: &Role) -> Vec<Self> {
let mut tx = db.begin().await.unwrap();
let ret = Self::all_with_role_tx(&mut tx, role).await;
tx.commit().await.unwrap();
ret
}
pub async fn all_with_role_tx(db: &mut Transaction<'_, Sqlite>, role: &Role) -> Vec<Self> {
sqlx::query_as!( sqlx::query_as!(
Self, Self,
" "
@ -380,7 +387,7 @@ WHERE ur.role_id = ? AND deleted = 0
ORDER BY name; ORDER BY name;
", role.id ", role.id
) )
.fetch_all(db) .fetch_all(db.deref_mut())
.await .await
.unwrap() .unwrap()
} }

View File

@ -222,56 +222,58 @@
<div class="text-sm text-gray-600 dark:text-gray-100"> <div class="text-sm text-gray-600 dark:text-gray-100">
Ruderer: Ruderer:
{% for rower in log.rowers -%} {% for rower in log.rowers -%}
{{ rower.name -}} {{ rower.name }}
{% if not loop.last or amount_guests > 0 and log.boat.name != 'Externes Boot' %},{% endif %} {%- if rower.id == log.steering_user.id and rower.id != log.shipmaster_user.id %}
{% endfor %} (Steuerperson){%- endif -%}
{% if amount_guests > 0 and log.boat.name != 'Externes Boot' %} {%- if not loop.last or amount_guests > 0 and log.boat.name != 'Externes Boot' %},{% endif %}
Gäste {% endfor -%}
<small class="text-gray-600 dark:text-gray-100">(ohne Account)</small>: {% if amount_guests > 0 and log.boat.name != 'Externes Boot' %}
{{ amount_guests }} Gäste
{% endif %} <small class="text-gray-600 dark:text-gray-100">(ohne Account)</small>:
</div> {{ amount_guests }}
{% endif %}
</div>
{% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %}
</div>
</div>
{% endmacro show_old %}
{% macro home(log) %}
<form class="grid grid-cols-1 gap-3"
action="/log/{{ log.id }}"
method="post">
{{ macros::input(label='Ankunftszeit', name='arrival', type='datetime-local', required=true, class="change-id-js rounded-md current-date-time") }}
<div>
<label for="destination" class="text-sm text-gray-600 dark:text-gray-100">Ziel</label>
<input class="input rounded-md set-distance-js change-id-js"
type="search"
list="destinations"
placeholder="Destination"
required="required"
id="destination{{ log.id }}"
name="destination"
value="{{ log.destination }}"
data-relation="distance_in_km{{ log.id }}" />
</div>
<div class="relative">
{{ macros::input(label="Distanz", name="distance_in_km", id="distance_in_km" ~ log.id , type="number", min=0, value=log.distance_in_km, required=true, class="rounded-md change-id-js") }}
<span class="absolute right-0 bottom-0 py-1.5 px-2 bg-white dark:bg-primary-950 border-0 text-gray-600 dark:text-gray-100 ring-1 ring-inset ring-gray-300 dark:ring-primary-950 rounded-br-md rounded-tr-md">km</span>
</div>
{{ macros::input(label="Kommentar", name="comments", id="comments" ~ log.id, type="text", value=log.comments, class="rounded-md change-id-js") }}
<details class="bg-gray-200 bg-opacity-80 dark:bg-primary-900 rounded-md p-2">
<summary class="cursor-pointer">Details ändern</summary>
<div class="grid grid-cols-1 gap-3">
{{ macros::input(label='Abfahrtszeit', name='departure', type='datetime-local', required=true, class="change-id-js rounded-md", value=log.departure) }}
{{ log::rower_select(id="rowers"~log.id, selected=log.rowers, amount_seats=log.boat.amount_seats, steering_person_id=log.steering_user.id, cox_on_boat=log.shipmaster_user.id) }}
{{ macros::select(label="Schiffsführer", data=[], name='shipmaster', id="shipmaster-rowers"~log.id, class="change-id-js", selected_id=log.shipmaster_user.id, required=true) }}
{{ macros::select(label="Steuerperson", data=[], name='steering_person', id="steering_person-rowers"~log.id, class="change-id-js", selected_id=log.steering_user.id, required=true) }}
<div>
{{ macros::checkbox(label="Handgesteuert", name="shipmaster_only_steering", id="shipmaster_only_steering" ~ log.id , checked=log.shipmaster_only_steering,class="rounded-md change-id-js") }}
</div>
{{ macros::select(label="Typ", data=logtypes, name="logtype", id="logtype" ~ log.id, default="Normal", selected_id=log.logtype, class="rounded-md change-id-js") }}
</div> </div>
</details> </div>
<input class="btn btn-primary" type="submit" value="Ausfahrt beenden" /> {% endmacro show_old %}
</form> {% macro home(log) %}
{% endmacro home %} <form class="grid grid-cols-1 gap-3"
action="/log/{{ log.id }}"
method="post">
{{ macros::input(label='Ankunftszeit', name='arrival', type='datetime-local', required=true, class="change-id-js rounded-md current-date-time") }}
<div>
<label for="destination" class="text-sm text-gray-600 dark:text-gray-100">Ziel</label>
<input class="input rounded-md set-distance-js change-id-js"
type="search"
list="destinations"
placeholder="Destination"
required="required"
id="destination{{ log.id }}"
name="destination"
value="{{ log.destination }}"
data-relation="distance_in_km{{ log.id }}" />
</div>
<div class="relative">
{{ macros::input(label="Distanz", name="distance_in_km", id="distance_in_km" ~ log.id , type="number", min=0, value=log.distance_in_km, required=true, class="rounded-md change-id-js") }}
<span class="absolute right-0 bottom-0 py-1.5 px-2 bg-white dark:bg-primary-950 border-0 text-gray-600 dark:text-gray-100 ring-1 ring-inset ring-gray-300 dark:ring-primary-950 rounded-br-md rounded-tr-md">km</span>
</div>
{{ macros::input(label="Kommentar", name="comments", id="comments" ~ log.id, type="text", value=log.comments, class="rounded-md change-id-js") }}
<details class="bg-gray-200 bg-opacity-80 dark:bg-primary-900 rounded-md p-2">
<summary class="cursor-pointer">Details ändern</summary>
<div class="grid grid-cols-1 gap-3">
{{ macros::input(label='Abfahrtszeit', name='departure', type='datetime-local', required=true, class="change-id-js rounded-md", value=log.departure) }}
{{ log::rower_select(id="rowers"~log.id, selected=log.rowers, amount_seats=log.boat.amount_seats, steering_person_id=log.steering_user.id, cox_on_boat=log.shipmaster_user.id) }}
{{ macros::select(label="Schiffsführer", data=[], name='shipmaster', id="shipmaster-rowers"~log.id, class="change-id-js", selected_id=log.shipmaster_user.id, required=true) }}
{{ macros::select(label="Steuerperson", data=[], name='steering_person', id="steering_person-rowers"~log.id, class="change-id-js", selected_id=log.steering_user.id, required=true) }}
<div>
{{ macros::checkbox(label="Handgesteuert", name="shipmaster_only_steering", id="shipmaster_only_steering" ~ log.id , checked=log.shipmaster_only_steering,class="rounded-md change-id-js") }}
</div>
{{ macros::select(label="Typ", data=logtypes, name="logtype", id="logtype" ~ log.id, default="Normal", selected_id=log.logtype, class="rounded-md change-id-js") }}
</div>
</details>
<input class="btn btn-primary" type="submit" value="Ausfahrt beenden" />
</form>
{% endmacro home %}