use chrono::{Datelike, NaiveDate, Weekday}; use serde::{Deserialize, Serialize}; use crate::schedule::ShiftType; #[derive(Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq, Debug, Hash, Clone, Copy)] #[serde(rename_all = "camelCase")] pub struct Slot { pub day: Day, pub position: ShiftPosition, } impl Slot { pub fn new(day: Day, position: ShiftPosition) -> Self { Self { day, position } } pub fn is_open_shift(&self) -> bool { self.day.is_open_shift() } pub fn is_first(&self) -> bool { self.day == Day(1) && self.position == ShiftPosition::First } pub fn is_first_day(&self) -> bool { self.day == Day(1) } pub fn is_open_first(&self) -> bool { self.is_open_shift() && self.position == ShiftPosition::First } pub fn is_open_second(&self) -> bool { self.is_open_shift() && self.position == ShiftPosition::Second } pub fn shift_type_str(&self) -> String { match (self.day.is_open_shift(), self.position) { (true, ShiftPosition::First) => "Ανοιχτή(1)".to_string(), (true, ShiftPosition::Second) => "Ανοιχτή(2)".to_string(), _ => "Κλειστή".to_string(), } } pub fn next(&self) -> Self { match self.position { ShiftPosition::First if self.is_open_shift() => Self { day: self.day, position: ShiftPosition::Second, }, ShiftPosition::First | ShiftPosition::Second => Self { day: self.day.next(), position: ShiftPosition::First, }, } } pub fn previous(&self) -> Self { match self.position { ShiftPosition::First => { let past_day = self.day.previous(); if past_day.is_open_shift() { Self { day: past_day, position: ShiftPosition::Second, } } else { Self { day: past_day, position: ShiftPosition::First, } } } ShiftPosition::Second => Self { day: self.day, position: ShiftPosition::First, }, } } pub fn greater_than(&self, limit: u8) -> bool { self.day.greater_than(&Day(limit)) } pub fn other_position(&self) -> Option { if !self.day.is_open_shift() { return None; } let other_pos = match self.position { ShiftPosition::First => ShiftPosition::Second, ShiftPosition::Second => ShiftPosition::First, }; Some(Self { day: self.day, position: other_pos, }) } pub fn shift_type(&self) -> ShiftType { match (self.is_open_shift(), self.position) { (true, ShiftPosition::First) => ShiftType::OpenFirst, (true, ShiftPosition::Second) => ShiftType::OpenSecond, (false, _) => ShiftType::Closed, } } } impl Default for Slot { fn default() -> Self { Self { day: Day(1), position: ShiftPosition::First, } } } impl From for Slot { fn from(value: u8) -> Self { let mut slot = Slot::default(); for _ in 0..value { slot = slot.next() } slot } } #[derive(Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq, Debug, Hash, Clone, Copy)] #[serde(rename_all = "camelCase")] pub struct Day(pub u8); impl Day { pub fn is_open_shift(&self) -> bool { !self.0.is_multiple_of(2) } pub fn shift_types(&self) -> &[ShiftType] { if self.is_open_shift() { &[ShiftType::OpenFirst, ShiftType::OpenSecond] } else { &[ShiftType::Closed] } } pub fn next(&self) -> Self { Self(self.0 + 1) } pub fn previous(&self) -> Self { if self.0 <= 1 { Self(1) } else { Self(self.0 - 1) } } pub fn greater_than(&self, other: &Day) -> bool { self.0 > other.0 } pub fn is_weekend(&self, month: u32, year: i32) -> bool { let date = NaiveDate::from_ymd_opt(year, month, self.0 as u32); match date { Some(date) => { let weekday = date.weekday(); weekday == Weekday::Sat || weekday == Weekday::Sun } None => false, } } pub fn weekday_to_greek(&self, month: u32, year: i32) -> &'static str { let date = NaiveDate::from_ymd_opt(year, month, self.0 as u32).unwrap(); match date.weekday() { Weekday::Mon => "Δευτέρα", Weekday::Tue => "Τρίτη", Weekday::Wed => "Τετάρτη", Weekday::Thu => "Πέμπτη", Weekday::Fri => "Παρασκευή", Weekday::Sat => "Σάββατο", Weekday::Sun => "Κυριακή", } } } #[derive(Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq, Debug, Hash, Clone, Copy)] pub enum ShiftPosition { First, Second, } impl ShiftPosition { pub fn as_str(self) -> &'static str { match self { ShiftPosition::First => "First", ShiftPosition::Second => "Second", } } } impl TryFrom<&str> for ShiftPosition { type Error = (); fn try_from(s: &str) -> Result { match s { "First" => Ok(ShiftPosition::First), "Second" => Ok(ShiftPosition::Second), _ => Err(()), } } } #[cfg(test)] mod tests { use rstest::rstest; use crate::{ schedule::ShiftType, slot::{Day, ShiftPosition, Slot}, }; #[rstest] fn test_slot() { let slot_1 = Slot::new(Day(1), ShiftPosition::First); let slot_2 = Slot::new(Day(1), ShiftPosition::Second); let slot_3 = Slot::new(Day(2), ShiftPosition::First); assert!(slot_1.is_open_shift()); assert!(slot_2.is_open_shift()); assert!(!slot_3.is_open_shift()); assert!(slot_1.is_first()); assert!(!slot_2.is_first()); assert!(!slot_3.is_first()); assert!(slot_1.is_first_day()); assert!(slot_2.is_first_day()); assert!(!slot_3.is_first_day()); assert!(slot_1.is_open_first()); assert!(!slot_2.is_open_first()); assert!(!slot_3.is_open_first()); assert!(!slot_1.is_open_second()); assert!(slot_2.is_open_second()); assert!(!slot_3.is_open_second()); assert_eq!(slot_1.next(), slot_2); assert_eq!(slot_2.next(), slot_3); assert_eq!(slot_3.previous(), slot_2); assert_eq!(slot_2.previous(), slot_1); assert!(!slot_1.greater_than(1)); assert!(!slot_2.greater_than(1)); assert!(slot_3.greater_than(1)); assert_eq!(slot_1.other_position(), Some(slot_1.next())); assert_eq!(slot_2.other_position(), Some(slot_2.previous())); assert_eq!(slot_3.other_position(), None); assert_eq!(slot_1.shift_type(), ShiftType::OpenFirst); assert_eq!(slot_2.shift_type(), ShiftType::OpenSecond); assert_eq!(slot_3.shift_type(), ShiftType::Closed); } #[rstest] fn test_day() { let day_1 = Day(1); let day_2 = Day(2); let day_3 = Day(3); assert!(day_1.is_open_shift()); assert!(!day_2.is_open_shift()); assert!(day_3.is_open_shift()); assert_eq!(day_1.next(), day_2); assert_eq!(day_2.next(), day_3); assert_eq!(day_3.previous(), day_2); assert_eq!(day_2.previous(), day_1); assert!(!day_1.greater_than(&day_1)); assert!(day_2.greater_than(&day_1)); assert!(day_3.greater_than(&day_1)); assert!(day_1.is_weekend(2, 2026)); assert!(!day_2.is_weekend(2, 2026)); assert!(!day_3.is_weekend(2, 2026)); } }