staging #716
@@ -102,6 +102,28 @@ test("Cox can start and finish trip", async ({ page }, testInfo) => {
 | 
			
		||||
  await expect(page.locator('body')).toContainText('(cox2)');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  //Ausloggen...
 | 
			
		||||
  await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
 | 
			
		||||
  await page.getByRole('link', { name: 'Ausloggen' }).click();
 | 
			
		||||
  // Login as admin
 | 
			
		||||
  await page.getByPlaceholder("Name").click();
 | 
			
		||||
  await page.getByPlaceholder("Name").fill("main");
 | 
			
		||||
  await page.getByPlaceholder("Name").press("Tab");
 | 
			
		||||
  await page.getByPlaceholder("Passwort").fill("admin");
 | 
			
		||||
  await page.getByPlaceholder("Passwort").press("Enter");
 | 
			
		||||
 | 
			
		||||
  await page.goto("/log/show");
 | 
			
		||||
  await page.getByText('(cox2)').click();
 | 
			
		||||
  page.once("dialog", (dialog) => {
 | 
			
		||||
    dialog.accept().catch(() => {});
 | 
			
		||||
  });
 | 
			
		||||
  await page.getByRole('link', { name: 'Löschen' }).click();
 | 
			
		||||
 | 
			
		||||
  //Ausloggen...
 | 
			
		||||
  await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
 | 
			
		||||
  await page.getByRole('link', { name: 'Ausloggen' }).click();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("Kiosk can start and cancel trip", async ({ page }, testInfo) => {
 | 
			
		||||
@@ -189,6 +211,29 @@ test("Kiosk can start and finish trip", async ({ page }, testInfo) => {
 | 
			
		||||
  await expect(page.locator('body')).toContainText('(cox2)');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  //Ausloggen...
 | 
			
		||||
  await page.context().clearCookies();
 | 
			
		||||
  await page.goto("/auth");
 | 
			
		||||
  // Login as admin
 | 
			
		||||
  await page.getByPlaceholder("Name").click();
 | 
			
		||||
  await page.getByPlaceholder("Name").fill("main");
 | 
			
		||||
  await page.getByPlaceholder("Name").press("Tab");
 | 
			
		||||
  await page.getByPlaceholder("Passwort").fill("admin");
 | 
			
		||||
  await page.getByPlaceholder("Passwort").press("Enter");
 | 
			
		||||
 | 
			
		||||
  await page.goto("/log/show");
 | 
			
		||||
  await page.getByText('(cox2)').click();
 | 
			
		||||
  page.once("dialog", (dialog) => {
 | 
			
		||||
    dialog.accept().catch(() => {});
 | 
			
		||||
  });
 | 
			
		||||
  await page.getByRole('link', { name: 'Löschen' }).click();
 | 
			
		||||
 | 
			
		||||
  //Ausloggen...
 | 
			
		||||
  await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
 | 
			
		||||
  await page.getByRole('link', { name: 'Ausloggen' }).click();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("Cox can start and finish trip with cox steering only", async ({ page }, testInfo) => {
 | 
			
		||||
@@ -241,8 +286,31 @@ test("Cox can start and finish trip with cox steering only", async ({ page }, te
 | 
			
		||||
 | 
			
		||||
  await page.goto('/log/show');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('cox_only_steering_boat');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('(cox2)');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('(cox2 - handgesteuert)');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  //Ausloggen...
 | 
			
		||||
  await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
 | 
			
		||||
  await page.getByRole('link', { name: 'Ausloggen' }).click();
 | 
			
		||||
  // Login as admin
 | 
			
		||||
  await page.getByPlaceholder("Name").click();
 | 
			
		||||
  await page.getByPlaceholder("Name").fill("main");
 | 
			
		||||
  await page.getByPlaceholder("Name").press("Tab");
 | 
			
		||||
  await page.getByPlaceholder("Passwort").fill("admin");
 | 
			
		||||
  await page.getByPlaceholder("Passwort").press("Enter");
 | 
			
		||||
 | 
			
		||||
  await page.goto("/log/show");
 | 
			
		||||
  await page.getByText('(cox2 - handgesteuert)').click();
 | 
			
		||||
  page.once("dialog", (dialog) => {
 | 
			
		||||
    dialog.accept().catch(() => {});
 | 
			
		||||
  });
 | 
			
		||||
  await page.getByRole('link', { name: 'Löschen' }).click();
 | 
			
		||||
 | 
			
		||||
  //Ausloggen...
 | 
			
		||||
  await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
 | 
			
		||||
  await page.getByRole('link', { name: 'Ausloggen' }).click();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("Kiosk can start and finish trip in one stop", async ({ page }, testInfo) => {
 | 
			
		||||
@@ -289,4 +357,27 @@ test("Kiosk can start and finish trip in one stop", async ({ page }, testInfo) =
 | 
			
		||||
  await expect(page.locator('body')).toContainText('(cox2)');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('a (1 km)');
 | 
			
		||||
  await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  //Ausloggen...
 | 
			
		||||
  await page.context().clearCookies();
 | 
			
		||||
  await page.goto("/auth");
 | 
			
		||||
  // Login as admin
 | 
			
		||||
  await page.getByPlaceholder("Name").click();
 | 
			
		||||
  await page.getByPlaceholder("Name").fill("main");
 | 
			
		||||
  await page.getByPlaceholder("Name").press("Tab");
 | 
			
		||||
  await page.getByPlaceholder("Passwort").fill("admin");
 | 
			
		||||
  await page.getByPlaceholder("Passwort").press("Enter");
 | 
			
		||||
 | 
			
		||||
  await page.goto("/log/show");
 | 
			
		||||
  await page.getByText('(cox2)').click();
 | 
			
		||||
  page.once("dialog", (dialog) => {
 | 
			
		||||
    dialog.accept().catch(() => {});
 | 
			
		||||
  });
 | 
			
		||||
  await page.getByRole('link', { name: 'Löschen' }).click();
 | 
			
		||||
 | 
			
		||||
  //Ausloggen...
 | 
			
		||||
  await page.getByRole('banner').getByRole('link', { name: 'Logbuch' }).click();
 | 
			
		||||
  await page.getByRole('link', { name: 'Ausloggen' }).click();
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ INSERT INTO "role" (name) VALUES ('Vorstand');
 | 
			
		||||
INSERT INTO "role" (name) VALUES ('Bootsführer');
 | 
			
		||||
INSERT INTO "role" (name) VALUES ('schnupperant');
 | 
			
		||||
INSERT INTO "role" (name) VALUES ('kassier');
 | 
			
		||||
INSERT INTO "role" (name) VALUES ('schriftfuehrer');
 | 
			
		||||
INSERT INTO "role" (name) VALUES ('no-einschreibgebuehr');
 | 
			
		||||
INSERT INTO "user" (name, pw) VALUES('admin', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$4P4NCw4Ukhv80/eQYTsarHhnw61JuL1KMx/L9dm82YM');
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(1,1);
 | 
			
		||||
@@ -37,7 +38,12 @@ INSERT INTO "user_role" (user_id, role_id) VALUES(8,5);
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(8,7);
 | 
			
		||||
INSERT INTO "user" (name,  pw) VALUES('Vorstandsmitglied', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY');
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(9,5);
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(9,9);
 | 
			
		||||
INSERT INTO "user" (name, pw) VALUES('main', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$4P4NCw4Ukhv80/eQYTsarHhnw61JuL1KMx/L9dm82YM');
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(10,1);
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(10,2);
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(10,5);
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(10,6);
 | 
			
		||||
INSERT INTO "user_role" (user_id, role_id) VALUES(10,9);
 | 
			
		||||
 | 
			
		||||
INSERT INTO "trip_details" (planned_starting_time, max_people, day, notes) VALUES('10:00', 2, '1970-01-01', 'trip_details for a planned event');
 | 
			
		||||
INSERT INTO "planned_event" (name, planned_amount_cox, trip_details_id) VALUES('test-planned-event', 2, 1);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
use std::ops::DerefMut;
 | 
			
		||||
 | 
			
		||||
use chrono::NaiveDateTime;
 | 
			
		||||
use itertools::Itertools;
 | 
			
		||||
use rocket::serde::{Deserialize, Serialize};
 | 
			
		||||
use rocket::FromForm;
 | 
			
		||||
@@ -391,6 +392,39 @@ ORDER BY amount_seats DESC
 | 
			
		||||
        .await
 | 
			
		||||
        .ok()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn on_water_between(
 | 
			
		||||
        &self,
 | 
			
		||||
        db: &mut Transaction<'_, Sqlite>,
 | 
			
		||||
        dep: NaiveDateTime,
 | 
			
		||||
        arr: NaiveDateTime,
 | 
			
		||||
    ) -> bool {
 | 
			
		||||
        let dep = dep.format("%Y-%m-%dT%H:%M").to_string();
 | 
			
		||||
        let arr = arr.format("%Y-%m-%dT%H:%M").to_string();
 | 
			
		||||
 | 
			
		||||
        sqlx::query!(
 | 
			
		||||
            "SELECT COUNT(*) AS overlap_count
 | 
			
		||||
FROM logbook
 | 
			
		||||
WHERE boat_id = ?
 | 
			
		||||
  AND (
 | 
			
		||||
    (departure <= ? AND arrival >= ?)  -- Existing entry covers the entire new period
 | 
			
		||||
    OR (departure >= ? AND departure < ?)  -- Existing entry starts during the new period
 | 
			
		||||
    OR (arrival > ? AND arrival <= ?)  -- Existing entry ends during the new period
 | 
			
		||||
  );",
 | 
			
		||||
            self.id,
 | 
			
		||||
            arr,
 | 
			
		||||
            arr,
 | 
			
		||||
            dep,
 | 
			
		||||
            dep,
 | 
			
		||||
            dep,
 | 
			
		||||
            arr
 | 
			
		||||
        )
 | 
			
		||||
        .fetch_one(db.deref_mut())
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .overlap_count
 | 
			
		||||
            > 0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
 
 | 
			
		||||
@@ -142,6 +142,7 @@ pub enum LogbookUpdateError {
 | 
			
		||||
    TooFast(i64, i64),
 | 
			
		||||
    AlreadyFinalized,
 | 
			
		||||
    ExternalSteeringPersonMustSteerOrShipmaster,
 | 
			
		||||
    BoatAlreadyOnWater,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
@@ -196,6 +197,7 @@ impl From<LogbookUpdateError> for LogbookCreateError {
 | 
			
		||||
            LogbookUpdateError::ExternalSteeringPersonMustSteerOrShipmaster => {
 | 
			
		||||
                LogbookCreateError::ExternalSteeringPersonMustSteerOrShipmaster
 | 
			
		||||
            }
 | 
			
		||||
            LogbookUpdateError::BoatAlreadyOnWater => LogbookCreateError::BoatAlreadyOnWater,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -573,6 +575,12 @@ ORDER BY departure DESC
 | 
			
		||||
            return Err(LogbookUpdateError::ArrivalNotAfterDeparture);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !boat.external {
 | 
			
		||||
            if boat.on_water_between(db, dep, arr).await {
 | 
			
		||||
                return Err(LogbookUpdateError::BoatAlreadyOnWater);
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let duration_in_mins = (arr.and_utc().timestamp() - dep.and_utc().timestamp()) / 60;
 | 
			
		||||
        // Not possible to row < 1 min / 500 m = < 2 min / km
 | 
			
		||||
        let possible_distance_km = duration_in_mins / 2;
 | 
			
		||||
 
 | 
			
		||||
@@ -1106,7 +1106,7 @@ mod test {
 | 
			
		||||
    fn test_cox() {
 | 
			
		||||
        let pool = testdb!();
 | 
			
		||||
        let res = User::cox(&pool).await;
 | 
			
		||||
        assert_eq!(res.len(), 3);
 | 
			
		||||
        assert_eq!(res.len(), 4);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[sqlx::test]
 | 
			
		||||
 
 | 
			
		||||
@@ -347,6 +347,7 @@ async fn home_logbook(
 | 
			
		||||
        Err(LogbookUpdateError::TooFast(km, min)) => Flash::error(Redirect::to("/log"), format!("KM zu groß für die eingegebene Dauer ({km} km in {min} Minuten). Bitte überprüfe deine Start- und Endzeit und versuche es erneut.")),
 | 
			
		||||
        Err(LogbookUpdateError::AlreadyFinalized) => Flash::error(Redirect::to("/log"), "Logbucheintrag wurde bereits abgeschlossen."),
 | 
			
		||||
        Err(LogbookUpdateError::ExternalSteeringPersonMustSteerOrShipmaster) => Flash::error(Redirect::to("/log"), "Wenn du eine 'Externe Steuerperson' hinzufügst, muss diese steuern oder Schiffsführer sein!"),
 | 
			
		||||
        Err(LogbookUpdateError::BoatAlreadyOnWater) => Flash::error(Redirect::to("/log"), "Das Boot war in diesem Zeitraum schon am Wasser. Bitte überprüfe das Datum und die Zeit."),
 | 
			
		||||
        Err(e) => Flash::error(
 | 
			
		||||
            Redirect::to("/log"),
 | 
			
		||||
            format!("Eintrag {logbook_id} konnte nicht abgesendet werden (Fehler: {e:?})!"),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user