use std::collections::HashMap; use crate::model::user::User; use chrono::Datelike; use serde::Serialize; use sqlx::{FromRow, Row, SqlitePool}; use super::boat::Boat; #[derive(Serialize, Clone)] pub struct BoatStat { pot_years: Vec, boats: Vec, } #[derive(Serialize, Clone)] pub struct SingleBoatStat { name: String, location: String, owner: String, years: HashMap, } impl BoatStat { pub async fn get(db: &SqlitePool) -> BoatStat { let mut years = Vec::new(); let mut boat_stats_map: HashMap = HashMap::new(); let rows = sqlx::query( " SELECT boat.id, location.name AS location, CAST(strftime('%Y', COALESCE(arrival, 'now')) AS INTEGER) AS year, CAST(SUM(COALESCE(distance_in_km, 0)) AS INTEGER) AS rowed_km FROM boat LEFT JOIN logbook ON boat.id = logbook.boat_id AND logbook.arrival IS NOT NULL LEFT JOIN location ON boat.location_id = location.id WHERE not boat.external GROUP BY boat.id, year ORDER BY boat.name, year DESC; ", ) .fetch_all(db) .await .unwrap(); for row in rows { let id: i32 = row.get("id"); let boat = Boat::find_by_id(db, id).await.unwrap(); let owner = if let Some(owner) = boat.owner(db).await { owner.name } else { String::from("Verein") }; let name = boat.name.clone(); let location: String = row.get("location"); let year: i32 = row.get("year"); if year == 0 { continue; // Boat still on water } if !years.contains(&year) { years.push(year); } let year: String = format!("{year}"); let rowed_km: i32 = row.get("rowed_km"); let boat_stat = boat_stats_map .entry(name.clone()) .or_insert(SingleBoatStat { name, location, owner, years: HashMap::new(), }); boat_stat.years.insert(year, rowed_km); } BoatStat { pot_years: years, boats: boat_stats_map.into_values().collect(), } } } #[derive(FromRow, Serialize, Clone)] pub struct Stat { name: String, rowed_km: i32, } impl Stat { pub async fn guest(db: &SqlitePool, year: Option) -> 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 rowed_km = sqlx::query(&format!( " SELECT SUM((b.amount_seats - COALESCE(m.member_count, 0)) * l.distance_in_km) as total_guest_km FROM logbook l JOIN boat b ON l.boat_id = b.id LEFT JOIN ( SELECT logbook_id, COUNT(*) as member_count FROM rower GROUP BY logbook_id ) m ON l.id = m.logbook_id WHERE l.distance_in_km IS NOT NULL AND l.arrival LIKE '{year}-%' AND not b.external; " )) .fetch_one(db) .await .unwrap() .get::(0) as i32; let rowed_km_guests = sqlx::query(&format!( " SELECT CAST(SUM(l.distance_in_km) AS INTEGER) AS rowed_km FROM user u INNER JOIN rower r ON u.id = r.rower_id INNER JOIN logbook l ON r.logbook_id = l.id WHERE u.id NOT IN ( SELECT ur.user_id FROM user_role ur INNER JOIN role ro ON ur.role_id = ro.id WHERE ro.name = 'Donau Linz' ) AND l.distance_in_km IS NOT NULL AND l.arrival LIKE '{year}-%' AND u.name != 'Externe Steuerperson'; " )) .fetch_one(db) .await .unwrap() .get::(0) as i32; Stat { name: "Gäste".into(), rowed_km: rowed_km + rowed_km_guests, } } pub async fn sum_people(db: &SqlitePool, year: Option) -> i32 { let stats = Self::people(db, year).await; let mut sum = 0; for stat in stats { sum += stat.rowed_km; } sum } pub async fn people(db: &SqlitePool, year: Option) -> Vec { 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) sqlx::query(&format!( " SELECT u.name, CAST(SUM(l.distance_in_km) AS INTEGER) AS rowed_km FROM ( SELECT * FROM user WHERE id IN ( SELECT user_id FROM user_role JOIN role ON user_role.role_id = role.id WHERE role.name = 'Donau Linz' ) ) 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}-%' AND u.name != 'Externe Steuerperson' GROUP BY u.name ORDER BY rowed_km DESC, u.name; " )) .fetch_all(db) .await .unwrap() .into_iter() .map(|row| Stat { name: row.get("name"), rowed_km: row.get("rowed_km"), }) .collect() } } #[derive(Debug, Serialize)] pub struct PersonalStat { date: String, km: i32, } pub async fn get_personal(db: &SqlitePool, user: &User) -> Vec { sqlx::query(&format!( " SELECT departure_date as date, SUM(total_distance) OVER (ORDER BY departure_date) as km FROM ( SELECT date(l.departure) as departure_date, COALESCE(SUM(l.distance_in_km),0) as total_distance FROM logbook l LEFT JOIN rower r ON l.id = r.logbook_id WHERE l.shipmaster = {0} OR r.rower_id = {0} GROUP BY departure_date ) as subquery ORDER BY departure_date; ", user.id )) .fetch_all(db) .await .unwrap() .into_iter() .map(|row| PersonalStat { date: row.get("date"), km: row.get("km"), }) .collect() }