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_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, }, _ => 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) -> Self { let other_pos = match self.position { ShiftPosition::First => ShiftPosition::Second, ShiftPosition::Second => ShiftPosition::First, }; 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, } } } #[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 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).unwrap(); let weekday = date.weekday(); weekday == Weekday::Sat || weekday == Weekday::Sun } pub fn weekday(&self, month: u32, year: i32) -> Weekday { let date = NaiveDate::from_ymd_opt(year, month, self.0 as u32).unwrap(); date.weekday() } } #[derive(Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq, Debug, Hash, Clone, Copy)] pub enum ShiftPosition { First, Second, } pub fn weekday_to_greek(weekday: Weekday) -> &'static str { match weekday { Weekday::Mon => "Δευτέρα", Weekday::Tue => "Τρίτη", Weekday::Wed => "Τετάρτη", Weekday::Thu => "Πέμπτη", Weekday::Fri => "Παρασκευή", Weekday::Sat => "Σάββατο", Weekday::Sun => "Κυριακή", } } pub fn month_to_greek(month: u32) -> &'static str { match month { 1 => "Ιανουάριος", 2 => "Φεβρουάριος", 3 => "Μάρτιος", 4 => "Απρίλιος", 5 => "Μάιος", 6 => "Ιούνιος", 7 => "Ιούλιος", 8 => "Αύγουστος", 9 => "Σεπτέμβριος", 10 => "Οκτώβριος", 11 => "Νοέμβριος", 12 => "Δεκέμβριος", _ => panic!("Unable to find translation for month {}", month), } } #[cfg(test)] mod tests { use rstest::rstest; use crate::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_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)); } #[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)); } }