156 lines
4.1 KiB
Rust
156 lines
4.1 KiB
Rust
use anyhow::Context;
|
|
use chrono::Month;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
resident::{Resident, ResidentDTO, ResidentId},
|
|
slot::Day,
|
|
};
|
|
|
|
const MONTH: u8 = 2;
|
|
const YEAR: i32 = 2026;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ToxicPair((ResidentId, ResidentId));
|
|
|
|
impl ToxicPair {
|
|
pub fn new(res_id_1: u8, res_id_2: u8) -> Self {
|
|
Self((ResidentId(res_id_1), ResidentId(res_id_2)))
|
|
}
|
|
|
|
pub fn matches(&self, other: &ToxicPair) -> bool {
|
|
let p1 = &self.0;
|
|
let p2 = &other.0;
|
|
|
|
(p1.0 == p2.0 && p1.1 == p2.1) || (p1.0 == p2.1 && p1.1 == p2.0)
|
|
}
|
|
}
|
|
|
|
impl From<(ResidentId, ResidentId)> for ToxicPair {
|
|
fn from(value: (ResidentId, ResidentId)) -> Self {
|
|
Self((value.0, value.1))
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct UserConfigDTO {
|
|
month: u8,
|
|
year: i32,
|
|
holidays: Vec<u8>,
|
|
residents: Vec<ResidentDTO>,
|
|
toxic_pairs: Vec<(u8, u8)>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct UserConfig {
|
|
pub month: Month,
|
|
pub year: i32,
|
|
pub holidays: Vec<u8>,
|
|
pub residents: Vec<Resident>,
|
|
pub toxic_pairs: Vec<ToxicPair>,
|
|
|
|
pub total_days: u8,
|
|
pub total_slots: u8,
|
|
pub total_holiday_slots: u8,
|
|
}
|
|
|
|
impl UserConfig {
|
|
pub fn with_holidays(mut self, holidays: Vec<u8>) -> Self {
|
|
self.holidays = holidays;
|
|
self.total_holiday_slots = self.total_holiday_slots();
|
|
self
|
|
}
|
|
|
|
pub fn with_residents(mut self, residents: Vec<Resident>) -> Self {
|
|
self.residents = residents;
|
|
self
|
|
}
|
|
|
|
pub fn add(&mut self, resident: Resident) {
|
|
self.residents.push(resident);
|
|
}
|
|
|
|
pub fn with_toxic_pairs(mut self, toxic_pairs: Vec<ToxicPair>) -> Self {
|
|
self.toxic_pairs = toxic_pairs;
|
|
self
|
|
}
|
|
|
|
fn total_holiday_slots(&self) -> u8 {
|
|
(1..=self.total_days)
|
|
.filter(|&d| self.is_holiday_or_weekend_slot(d))
|
|
.map(|d| if Day(d).is_open_shift() { 2 } else { 1 })
|
|
.sum()
|
|
}
|
|
|
|
pub fn is_holiday_or_weekend_slot(&self, day: u8) -> bool {
|
|
let day = Day(day);
|
|
day.is_weekend(self.month.number_from_month(), self.year)
|
|
|| self.holidays.contains(&(day.0))
|
|
}
|
|
}
|
|
|
|
impl Default for UserConfig {
|
|
fn default() -> Self {
|
|
let month = Month::try_from(MONTH).unwrap();
|
|
|
|
let total_days = month.num_days(YEAR).unwrap();
|
|
|
|
let total_slots = (1..=total_days)
|
|
.map(|d| if Day(d).is_open_shift() { 2 } else { 1 })
|
|
.sum();
|
|
|
|
let total_holiday_slots = (1..=total_days)
|
|
.filter(|&d| Day(d).is_weekend(month.number_from_month(), YEAR))
|
|
.map(|d| if Day(d).is_open_shift() { 2 } else { 1 })
|
|
.sum();
|
|
|
|
Self {
|
|
month,
|
|
year: YEAR,
|
|
holidays: vec![],
|
|
residents: vec![],
|
|
toxic_pairs: vec![],
|
|
total_days,
|
|
total_slots,
|
|
total_holiday_slots,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<UserConfigDTO> for UserConfig {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: UserConfigDTO) -> Result<Self, Self::Error> {
|
|
let month = Month::try_from(value.month)?;
|
|
|
|
let total_days = month.num_days(value.year).context("Failed to parse")?;
|
|
|
|
let total_slots = (1..=total_days)
|
|
.map(|d| if Day(d).is_open_shift() { 2 } else { 1 })
|
|
.sum();
|
|
|
|
let total_holiday_slots = (1..=total_days)
|
|
.filter(|&d| {
|
|
Day(d).is_weekend(month.number_from_month(), value.year)
|
|
|| value.holidays.contains(&d)
|
|
})
|
|
.map(|d| if Day(d).is_open_shift() { 2 } else { 1 })
|
|
.sum();
|
|
|
|
Ok(Self {
|
|
month,
|
|
year: value.year,
|
|
holidays: value.holidays,
|
|
residents: value.residents.into_iter().map(Resident::from).collect(),
|
|
toxic_pairs: value
|
|
.toxic_pairs
|
|
.into_iter()
|
|
.map(|p| ToxicPair::new(p.0, p.1))
|
|
.collect(),
|
|
total_days,
|
|
total_slots,
|
|
total_holiday_slots,
|
|
})
|
|
}
|
|
}
|