Implement DTOs, add managed state for schedule, export to txt
This commit is contained in:
@@ -1,15 +1,25 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::Month;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
resident::Resident,
|
||||
resident::{Resident, ResidentDTO},
|
||||
schedule::{MonthlySchedule, ShiftType},
|
||||
slot::{Day, ShiftPosition, Slot},
|
||||
};
|
||||
|
||||
const YEAR: i32 = 2026;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct UserConfigDTO {
|
||||
month: usize,
|
||||
year: i32,
|
||||
holidays: Vec<usize>,
|
||||
residents: Vec<ResidentDTO>,
|
||||
toxic_pairs: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserConfig {
|
||||
month: Month,
|
||||
@@ -36,6 +46,18 @@ impl UserConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_dto(dto: UserConfigDTO) -> Self {
|
||||
Self {
|
||||
month: Month::try_from(dto.month as u8).unwrap(),
|
||||
year: dto.year,
|
||||
holidays: dto.holidays,
|
||||
residents: dto.residents.into_iter().map(Resident::from_dto).collect(),
|
||||
toxic_pairs: dto.toxic_pairs,
|
||||
workload_limits: HashMap::new(),
|
||||
holiday_limits: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_holidays(mut self, holidays: Vec<usize>) -> Self {
|
||||
self.holidays = holidays;
|
||||
self
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
// here lies the logic for the export of the final schedule into docx/pdf formats
|
||||
|
||||
use crate::schedule::MonthlySchedule;
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
use log::info;
|
||||
|
||||
use crate::{
|
||||
config::UserConfig,
|
||||
schedule::MonthlySchedule,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FileType {
|
||||
Txt,
|
||||
Json,
|
||||
Csv,
|
||||
Doc,
|
||||
@@ -11,16 +19,17 @@ pub enum FileType {
|
||||
}
|
||||
|
||||
pub trait Export {
|
||||
fn export(&self, file_type: FileType);
|
||||
fn export(&self, file_type: FileType, config: &UserConfig);
|
||||
}
|
||||
|
||||
impl Export for MonthlySchedule {
|
||||
fn export(&self, file_type: FileType) {
|
||||
fn export(&self, file_type: FileType, config: &UserConfig) {
|
||||
match file_type {
|
||||
FileType::Csv => self.export_as_csv(),
|
||||
FileType::Json => self.export_as_json(),
|
||||
FileType::Doc => self.export_as_doc(),
|
||||
FileType::Pdf => self.export_as_pdf(),
|
||||
FileType::Txt => self.export_as_txt(config),
|
||||
FileType::Csv => self.export_as_csv(config),
|
||||
FileType::Json => self.export_as_json(config),
|
||||
FileType::Doc => self.export_as_doc(config),
|
||||
FileType::Pdf => self.export_as_pdf(config),
|
||||
};
|
||||
|
||||
// TODO: make this env var from a config file? Option to change this in-app
|
||||
@@ -29,27 +38,35 @@ impl Export for MonthlySchedule {
|
||||
"exported type {:?}. Saved at folder path {}",
|
||||
file_type, env_path
|
||||
);
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl MonthlySchedule {
|
||||
// return error result as string or nothing, maybe use anyhow
|
||||
// or just return a string.. for now
|
||||
pub fn export_as_txt(&self, config: &UserConfig) -> String {
|
||||
let file = File::create("schedule.txt").unwrap();
|
||||
let mut writer = std::io::BufWriter::new(file);
|
||||
writer
|
||||
.write_all(self.pretty_print(config).as_bytes())
|
||||
.expect("Failed to write to buffer");
|
||||
|
||||
pub fn export_as_csv(&self) -> String {
|
||||
writer.flush().expect("Failed to flush buffer");
|
||||
info!("im here");
|
||||
"ok".to_string()
|
||||
}
|
||||
|
||||
pub fn export_as_csv(&self, config: &UserConfig) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn export_as_json(&self) -> String {
|
||||
pub fn export_as_json(&self, config: &UserConfig) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn export_as_doc(&self) -> String {
|
||||
pub fn export_as_doc(&self, config: &UserConfig) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn export_as_pdf(&self) -> String {
|
||||
pub fn export_as_pdf(&self, config: &UserConfig) -> String {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use log::info;
|
||||
|
||||
use crate::{
|
||||
config::UserConfig,
|
||||
config::{UserConfig, UserConfigDTO},
|
||||
export::{Export, FileType},
|
||||
generator::backtracking,
|
||||
schedule::MonthlySchedule,
|
||||
@@ -16,35 +18,46 @@ mod resident;
|
||||
mod schedule;
|
||||
mod slot;
|
||||
|
||||
struct AppState {
|
||||
schedule: Mutex<MonthlySchedule>,
|
||||
}
|
||||
|
||||
/// argument to this must be the rota state including all
|
||||
/// information for the residents, the forbidden pairs the holidays
|
||||
/// and the period of the schedule
|
||||
#[tauri::command]
|
||||
fn generate() -> String {
|
||||
let config = UserConfig::default();
|
||||
fn generate(config: UserConfigDTO, state: tauri::State<'_, AppState>) -> MonthlySchedule {
|
||||
info!("{:?}", config);
|
||||
let mut config = UserConfig::from_dto(config);
|
||||
config.calculate_workload_limits();
|
||||
config.calculate_holiday_limits();
|
||||
let mut schedule = MonthlySchedule::new();
|
||||
schedule.prefill(&config);
|
||||
|
||||
backtracking(&mut schedule, Slot::default(), &config);
|
||||
info!("{}", schedule.pretty_print(&config));
|
||||
|
||||
schedule.export_as_json()
|
||||
let solved = backtracking(&mut schedule, Slot::default(), &config);
|
||||
let mut internal_schedule = state.schedule.lock().unwrap();
|
||||
*internal_schedule = schedule.clone();
|
||||
assert!(solved);
|
||||
info!("{}", schedule.pretty_print(&config));
|
||||
|
||||
schedule
|
||||
}
|
||||
|
||||
/// export into docx
|
||||
#[tauri::command]
|
||||
fn export() -> String {
|
||||
let schedule = MonthlySchedule::new();
|
||||
let filetype = FileType::Doc;
|
||||
|
||||
schedule.export(filetype);
|
||||
|
||||
todo!()
|
||||
fn export(config: UserConfigDTO, state: tauri::State<'_, AppState>) {
|
||||
let config = UserConfig::from_dto(config);
|
||||
let schedule = state.schedule.lock().unwrap();
|
||||
schedule.export(FileType::Txt, &config);
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.manage(AppState {
|
||||
schedule: Mutex::new(MonthlySchedule::new()),
|
||||
})
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::new()
|
||||
.level(tauri_plugin_log::log::LevelFilter::Info)
|
||||
|
||||
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
schedule::ShiftType,
|
||||
slot::{Day, Slot},
|
||||
slot::{Day, ShiftPosition, Slot},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@@ -17,6 +17,18 @@ pub struct Resident {
|
||||
pub reduced_load: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResidentDTO {
|
||||
id: String,
|
||||
name: String,
|
||||
negative_shifts: Vec<usize>,
|
||||
manual_shifts: Vec<Slot>,
|
||||
max_shifts: Option<usize>,
|
||||
allowed_types: Vec<ShiftType>,
|
||||
reduced_load: bool,
|
||||
}
|
||||
|
||||
impl Resident {
|
||||
pub fn new(id: &str, name: &str) -> Self {
|
||||
Self {
|
||||
@@ -43,4 +55,30 @@ impl Resident {
|
||||
self.reduced_load = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn from_dto(dto: ResidentDTO) -> Self {
|
||||
Self {
|
||||
id: dto.id,
|
||||
name: dto.name,
|
||||
negative_shifts: dto
|
||||
.negative_shifts
|
||||
.into_iter()
|
||||
.map(|d| Day(d as u8))
|
||||
.collect(),
|
||||
manual_shifts: dto
|
||||
.manual_shifts
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
Slot {
|
||||
day: s.day,
|
||||
// FIXME: frontend always brings resident manual shifts as first
|
||||
position: ShiftPosition::First,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
max_shifts: dto.max_shifts,
|
||||
allowed_types: dto.allowed_types,
|
||||
reduced_load: dto.reduced_load,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,33 @@ use std::collections::HashMap;
|
||||
use crate::{
|
||||
config::UserConfig,
|
||||
resident::Resident,
|
||||
slot::{Day, Slot},
|
||||
slot::{Day, ShiftPosition, Slot},
|
||||
};
|
||||
|
||||
use serde::Serializer;
|
||||
|
||||
impl Serialize for MonthlySchedule {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
use serde::ser::SerializeMap;
|
||||
let mut map = serializer.serialize_map(Some(self.0.len()))?;
|
||||
for (slot, name) in &self.0 {
|
||||
let pos_str = match slot.position {
|
||||
ShiftPosition::First => "First",
|
||||
ShiftPosition::Second => "Second",
|
||||
};
|
||||
let key = format!("{}-{}", slot.day.0, pos_str);
|
||||
map.serialize_entry(&key, name)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
// each slot has one resident
|
||||
// a day can span between 1 or 2 slots depending on if it is open(odd) or closed(even)
|
||||
#[derive(Debug)]
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct MonthlySchedule(HashMap<Slot, String>);
|
||||
|
||||
impl MonthlySchedule {
|
||||
@@ -179,7 +200,7 @@ impl MonthlySchedule {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub enum ShiftType {
|
||||
Closed,
|
||||
OpenFirst,
|
||||
|
||||
Reference in New Issue
Block a user