use crate::{admin::team::Team, Station}; use chrono::{DateTime, Local, NaiveDateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, SqlitePool}; #[derive(FromRow, Debug, Serialize, Deserialize)] pub(crate) struct Rating { pub(crate) team_id: i64, pub(crate) station_id: i64, pub(crate) points: Option, pub(crate) notes: Option, arrived_at: NaiveDateTime, started_at: Option, left_at: Option, } impl Rating { pub(crate) async fn create( db: &SqlitePool, station: &Station, team: &Team, ) -> Result<(), String> { sqlx::query!( "INSERT INTO rating(team_id, station_id) VALUES (?, ?)", team.id, station.id ) .execute(db) .await .map_err(|e| e.to_string())?; Ok(()) } pub(crate) async fn create_quick(db: &SqlitePool, team: &Team, station: &Station, points: i64) { sqlx::query!( "INSERT INTO rating(team_id, station_id, points, arrived_at, started_at, left_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)", team.id, station.id, points ) .execute(db) .await .unwrap(); } pub(crate) async fn team(&self, db: &SqlitePool) -> Team { Team::find_by_id(db, self.team_id) .await .expect("db constraints") } pub(crate) async fn for_station(db: &SqlitePool, station: &Station) -> Vec { sqlx::query_as::<_, Self>("SELECT team_id, station_id, points, notes, arrived_at, started_at, left_at FROM rating WHERE station_id = ?;") .bind(station.id) .fetch_all(db) .await .unwrap() } pub(crate) async fn update( db: &SqlitePool, station: &Station, team: &Team, points: Option, notes: Option, ) -> Result<(), String> { sqlx::query!( "UPDATE rating SET points = ?, notes = ? WHERE station_id = ? AND team_id = ?", points, notes, station.id, team.id ) .execute(db) .await .map_err(|e| e.to_string())?; Ok(()) } pub(crate) async fn delete( db: &SqlitePool, station: &Station, team: &Team, ) -> Result<(), String> { sqlx::query!( "DELETE FROM rating WHERE team_id = ? AND station_id = ?", team.id, station.id ) .execute(db) .await .map_err(|e| e.to_string())?; Ok(()) } pub async fn find_by_team_and_station( db: &SqlitePool, team: &Team, station: &Station, ) -> Option { sqlx::query_as!(Self, "SELECT team_id, station_id, points, notes, arrived_at, started_at, left_at FROM rating WHERE team_id = ? AND station_id = ?", team.id, station.id) .fetch_one(db) .await .ok() } pub(crate) fn local_time_arrived_at(&self) -> String { let datetime_utc = DateTime::::from_naive_utc_and_offset(self.arrived_at, Utc); let datetime_local = datetime_utc.with_timezone(&Local); datetime_local.format("%H:%M").to_string() } pub(crate) fn local_time_doing(&self) -> String { let Some(started_at) = self.started_at else { return String::from("noch nicht gestartet"); }; let datetime_utc = DateTime::::from_naive_utc_and_offset(started_at, Utc); let datetime_local = datetime_utc.with_timezone(&Local); datetime_local.format("%H:%M").to_string() } pub(crate) fn local_time_left(&self) -> String { let Some(left_at) = self.left_at else { return String::from("noch nicht fertig"); }; let datetime_utc = DateTime::::from_naive_utc_and_offset(left_at, Utc); let datetime_local = datetime_utc.with_timezone(&Local); datetime_local.format("%H:%M").to_string() } } pub(crate) struct TeamsAtStationLocation { pub(crate) total_teams: i64, pub(crate) not_yet_here: Vec, pub(crate) waiting: Vec<(Team, Rating)>, pub(crate) doing: Vec<(Team, Rating)>, pub(crate) left_not_yet_rated: Vec<(Team, Rating)>, pub(crate) left_and_rated: Vec<(Team, Rating)>, } impl TeamsAtStationLocation { pub(crate) async fn for_station(db: &SqlitePool, station: &Station) -> TeamsAtStationLocation { let teams = station.teams(db).await; let total_teams = teams.len() as i64; let mut not_yet_here = Vec::new(); let mut waiting = Vec::new(); let mut doing = Vec::new(); let mut left_not_yet_rated = Vec::new(); let mut left_and_rated = Vec::new(); for team in teams { match Rating::find_by_team_and_station(db, &team, station).await { Some(rating) => { if rating.left_at.is_some() { if rating.points.is_some() { left_and_rated.push((team, rating)); } else { left_not_yet_rated.push((team, rating)); } } else if rating.started_at.is_some() { doing.push((team, rating)); } else { waiting.push((team, rating)); } } None => not_yet_here.push(team), } } TeamsAtStationLocation { total_teams, not_yet_here, waiting, doing, left_not_yet_rated, left_and_rated, } } }