295 lines
7.9 KiB
Rust
295 lines
7.9 KiB
Rust
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<Self> {
|
|
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<u8> 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<Self, Self::Error> {
|
|
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));
|
|
}
|
|
}
|