use core::panic; use std::io; use chrono::{offset::LocalResult, DateTime, Datelike, Duration, Local, TimeZone}; use sqlx::PgPool; use std::sync::Arc; use time::OffsetDateTime; use std::sync::Mutex; #[derive(Debug)] struct Hospital { id: i32, name: String, } // struct Schedule { // from: DateTime, // to: DateTime, // } // Helper function to print with timestamp fn log_with_timestamp(message: &str) { let now = Local::now(); println!("[{}] {}", now.format("%Y-%m-%d %H:%M:%S"), message); } // Static variable to store the last printed hospital ID static LAST_HOSPITAL_ID: Mutex> = Mutex::new(None); pub async fn create_hospital_list(pool: Arc) -> Result<(), sqlx::Error> { // Fetch hospitals from the database let hospitals = sqlx::query_as!( Hospital, r#" SELECT id, name FROM hospitals ORDER BY id "# ) .fetch_all(&*pool) .await?; if hospitals.is_empty() { log_with_timestamp("No hospitals found in the database."); return Ok(()); } // let mut start: DateTime = Local.with_ymd_and_hms(2025, 1, 6, 8, 0, 0).unwrap(); // set date for testing let mut start: DateTime = Local::now(); let mut end: DateTime = get_end_of_shift(start); log_with_timestamp(&format!("It is now {} and the current Shift is ending on {}", start.format("%A %d/%m/%Y %H:%M"), end.format("%A %d/%m/%Y %H:%M"))); log_with_timestamp("Which Hospital is currently on duty?"); // print out the hospitals for hospital in &hospitals { log_with_timestamp(&format!("{}) {}", hospital.id, hospital.name)); } let mut hospital_id_input = String::new(); io::stdin() .read_line(&mut hospital_id_input) .expect("Failed to read the line"); let hospital_id_input = hospital_id_input.trim(); let hospital_id: i32 = match hospital_id_input.parse() { Ok(num) => num, Err(_) => { log_with_timestamp("Please enter a valid number!"); return Ok(()); } }; // Find the index of the selected hospital let mut hospital_index: usize = 0; let mut found = false; for (i, hospital) in hospitals.iter().enumerate() { if hospital.id == hospital_id { hospital_index = i; found = true; break; } } if !found { log_with_timestamp(&format!("Hospital with ID {} not found.", hospital_id)); return Ok(()); } log_with_timestamp("Generating and storing shifts in the database..."); // Clear existing shifts sqlx::query("DELETE FROM shifts") .execute(&*pool) .await?; // Generate and store 100 shifts for i in 1..=100 { // Format the timestamps as strings for database storage let start_str = start.format("%Y-%m-%d %H:%M:%S").to_string(); let end_str = end.format("%Y-%m-%d %H:%M:%S").to_string(); // Store the current shift in the database sqlx::query( r#" INSERT INTO shifts (hospital_id, start_time, end_time) VALUES ($1, $2::timestamp with time zone, $3::timestamp with time zone) "#, ) .bind(hospitals[hospital_index].id) .bind(start_str) .bind(end_str) .execute(&*pool) .await?; // Print progress only every 10 shifts if i % 10 == 0 { log_with_timestamp(&format!("Stored {} shifts...", i)); } // new shift start = end; end = get_end_of_shift(start); // change hospital hospital_index = if hospital_index == 0 { 1 } else { 0 }; // If we only have one hospital, don't switch if hospitals.len() == 1 { hospital_index = 0; } } log_with_timestamp("Successfully stored 100 shifts in the database."); Ok(()) } pub async fn check_and_create_future_shifts(pool: Arc) -> Result<(), sqlx::Error> { // Get the current time let now = Local::now(); // Calculate the date two months from now let two_months_from_now = now + Duration::days(60); // Convert chrono DateTime to time OffsetDateTime for SQLx let now_offset = OffsetDateTime::from_unix_timestamp(now.timestamp()) .expect("Failed to convert to OffsetDateTime"); let two_months_offset = OffsetDateTime::from_unix_timestamp(two_months_from_now.timestamp()) .expect("Failed to convert to OffsetDateTime"); // Check if there are any shifts in the next two months let future_shifts_count = sqlx::query_scalar::<_, i64>( r#" SELECT COUNT(*) FROM shifts WHERE start_time > $1 AND start_time < $2 "#, ) .bind(now_offset) .bind(two_months_offset) .fetch_one(&*pool) .await?; // If there are no future shifts, create them if future_shifts_count == 0 { log_with_timestamp("No future shifts found. Creating shifts for the next two months..."); // Get the current hospital on duty let current_hospital = sqlx::query_as!( Hospital, r#" SELECT h.id, h.name FROM hospitals h JOIN shifts s ON h.id = s.hospital_id WHERE s.end_time > $1 ORDER BY s.end_time ASC LIMIT 1 "#, now_offset ) .fetch_optional(&*pool) .await?; // If no current hospital is found, get the first hospital from the database let hospital_id = match current_hospital { Some(hospital) => hospital.id, None => { let first_hospital = sqlx::query_as!( Hospital, r#" SELECT id, name FROM hospitals ORDER BY id LIMIT 1 "# ) .fetch_optional(&*pool) .await? .ok_or_else(|| sqlx::Error::RowNotFound)?; first_hospital.id } }; // Find the index of the selected hospital let hospitals = sqlx::query_as!( Hospital, r#" SELECT id, name FROM hospitals ORDER BY id "# ) .fetch_all(&*pool) .await?; let mut hospital_index = 0; for (i, hospital) in hospitals.iter().enumerate() { if hospital.id == hospital_id { hospital_index = i; break; } } // Generate shifts starting from now let mut start = now; let mut end = get_end_of_shift(start); // Generate shifts for the next two months (approximately 60 days) for _ in 1..=120 { // Convert chrono DateTime to time OffsetDateTime for SQLx let start_offset = OffsetDateTime::from_unix_timestamp(start.timestamp()) .expect("Failed to convert to OffsetDateTime"); let end_offset = OffsetDateTime::from_unix_timestamp(end.timestamp()) .expect("Failed to convert to OffsetDateTime"); // Store the current shift in the database sqlx::query( r#" INSERT INTO shifts (hospital_id, start_time, end_time) VALUES ($1, $2, $3) "#, ) .bind(hospitals[hospital_index].id) .bind(start_offset) .bind(end_offset) .execute(&*pool) .await?; // new shift start = end; end = get_end_of_shift(start); // change hospital hospital_index = if hospital_index == 0 { 1 } else { 0 }; // If we only have one hospital, don't switch if hospitals.len() == 1 { hospital_index = 0; } } log_with_timestamp("Successfully created future shifts for the next two months."); } else { log_with_timestamp(&format!("Found {} future shifts. No need to create more.", future_shifts_count)); } Ok(()) } pub async fn print_current_hospital(pool: Arc) -> Result<(), sqlx::Error> { // Get the current time let now = Local::now(); // Convert chrono DateTime to time OffsetDateTime for SQLx let now_offset = OffsetDateTime::from_unix_timestamp(now.timestamp()) .expect("Failed to convert to OffsetDateTime"); // Get the current hospital on duty let current_hospital = sqlx::query_as!( Hospital, r#" SELECT h.id, h.name FROM hospitals h JOIN shifts s ON h.id = s.hospital_id WHERE s.start_time <= $1 AND s.end_time > $1 ORDER BY s.end_time ASC LIMIT 1 "#, now_offset ) .fetch_optional(&*pool) .await?; // Get the last printed hospital ID let mut last_id = LAST_HOSPITAL_ID.lock().unwrap(); // Check if the hospital has changed let current_id = current_hospital.as_ref().map(|h| h.id); let has_changed = *last_id != current_id; // Only print if the hospital has changed if has_changed { match ¤t_hospital { Some(hospital) => { log_with_timestamp(&format!("Current hospital on duty: {} (ID: {})", hospital.name, hospital.id)); }, None => { log_with_timestamp("No hospital currently on duty."); } } // Update the last printed hospital ID *last_id = current_id; } Ok(()) } fn get_end_of_shift(start: DateTime) -> DateTime { let end: DateTime; // define weekday & hour let weekday_str: String = start.format("%u").to_string(); let weekday: u32 = weekday_str.parse().unwrap(); let hour_str: String = start.format("%H").to_string(); let hour: u32 = hour_str.parse().unwrap(); // From Monday to Thursday before 7:00 if weekday >= 1 && weekday <= 4 && hour < 7 { let next_day: DateTime = start; let year: i32 = next_day.year(); let month: u32 = next_day.month(); let day: u32 = next_day.day(); let local_result: LocalResult> = Local.with_ymd_and_hms(year, month, day, 7, 0, 0); end = match local_result { LocalResult::None => { panic!("The specified date and time do not exist."); }, LocalResult::Single(datetime) => datetime, LocalResult::Ambiguous(datetime1, _datetime2, ) => { datetime1 } }; // From Monday to Wednesday after 7:00 } else if weekday >= 1 && weekday <= 3 && hour >= 7 { let next_day: DateTime = start + Duration::days(1); let year: i32 = next_day.year(); let month: u32 = next_day.month(); let day: u32 = next_day.day(); let local_result: LocalResult> = Local.with_ymd_and_hms(year, month, day, 7, 0, 0); end = match local_result { LocalResult::None => { panic!("The specified date and time do not exist."); }, LocalResult::Single(datetime) => datetime, LocalResult::Ambiguous(datetime1, _datetime2, ) => { datetime1 } }; } else if weekday == 4 && hour >= 7 { let next_day: DateTime = start + Duration::days(1); let year: i32 = next_day.year(); let month: u32 = next_day.month(); let day: u32 = next_day.day(); let local_result: LocalResult> = Local.with_ymd_and_hms(year, month, day, 17, 0, 0); end = match local_result { LocalResult::None => { panic!("The specified date and time do not exist."); }, LocalResult::Single(datetime) => datetime, LocalResult::Ambiguous(datetime1, _datetime2, ) => { datetime1 } }; } else if weekday == 5 && hour < 17 { let next_day: DateTime = start; let year: i32 = next_day.year(); let month: u32 = next_day.month(); let day: u32 = next_day.day(); let local_result: LocalResult> = Local.with_ymd_and_hms(year, month, day, 17, 0, 0); end = match local_result { LocalResult::None => { panic!("The specified date and time do not exist."); }, LocalResult::Single(datetime) => datetime, LocalResult::Ambiguous(datetime1, _datetime2, ) => { datetime1 } }; // Friday after 17:00 } else { let count_days: i64 = 8 - weekday as i64; let next_day: DateTime = start + Duration::days(count_days); let year: i32 = next_day.year(); let month: u32 = next_day.month(); let day: u32 = next_day.day(); let local_result: LocalResult> = Local.with_ymd_and_hms(year, month, day, 7, 0, 0); end = match local_result { LocalResult::None => { panic!("The specified date and time do not exist."); }, LocalResult::Single(datetime) => datetime, LocalResult::Ambiguous(datetime1, _datetime2, ) => { datetime1 } }; } end }