use std::{fs::File, io::Write}; use anyhow::Context; use docx_rs::{Docx, Paragraph, Run, RunFonts, Table, TableCell, TableRow}; use crate::{ config::UserConfig, schedule::MonthlySchedule, slot::{month_to_greek, weekday_to_greek, Day, ShiftPosition, Slot}, workload::WorkloadTracker, }; #[derive(Debug)] pub enum FileType { Txt, Docx, } pub trait Export { fn export( &self, file_type: FileType, config: &UserConfig, tracker: &WorkloadTracker, ) -> anyhow::Result<()>; } impl Export for MonthlySchedule { fn export( &self, file_type: FileType, config: &UserConfig, tracker: &WorkloadTracker, ) -> anyhow::Result<()> { match file_type { FileType::Txt => self.export_as_txt(config, tracker)?, FileType::Docx => self.export_as_doc(config)?, }; Ok(()) } } impl MonthlySchedule { pub fn export_as_txt( &self, config: &UserConfig, tracker: &WorkloadTracker, ) -> anyhow::Result<()> { let file = File::create("rota.txt")?; let mut writer = std::io::BufWriter::new(file); writer.write_all(self.pretty_print(config).as_bytes())?; writer.write_all(self.report(config, tracker).as_bytes())?; writer.flush()?; Ok(()) } pub fn export_as_doc(&self, config: &UserConfig) -> anyhow::Result<()> { let path = std::path::Path::new("rota.docx"); let file = std::fs::File::create(path)?; let header = Table::new(vec![ TableRow::new(vec![TableCell::new().add_paragraph( Paragraph::new() .add_run(Run::new().bold().add_text("Εφημερίες Ακτινολογικό")) .fonts(RunFonts::new().ascii("Arial")), )]), TableRow::new(vec![TableCell::new().add_paragraph( Paragraph::new() .add_run(Run::new().bold().add_text(format!( "Μήνας {} {}", month_to_greek(config.month.number_from_month()), config.year ))) .fonts(RunFonts::new().ascii("Arial")), )]), TableRow::new(vec![TableCell::new().add_paragraph( Paragraph::new() .add_run(Run::new().bold().add_text("")) .fonts(RunFonts::new().ascii("Arial")), )]), TableRow::new(vec![TableCell::new().add_paragraph( Paragraph::new() .add_run( Run::new() .bold() .add_text("ΠΑΝΕΠΙΣΤΗΜΙΑΚΟ ΓΕΝΙΚΟ ΝΟΣΟΚΟΜΕΙΟ ΙΩΑΝΝΙΝΩΝ"), ) .fonts(RunFonts::new().ascii("Arial")), )]), ]); let mut doc = Docx::new().add_table(header); let mut residents_table = Table::new(vec![]); for d in 1..=config.total_days { let day = Day(d); let is_weekend = day.is_weekend(config.month.number_from_month(), config.year); let slot_first = Slot::new(Day(d), ShiftPosition::First); let slot_first_res_id = self.get_resident_id(&slot_first); let res_name_1 = config .residents .iter() .find(|r| Some(&r.id) == slot_first_res_id) .map(|r| r.name.as_str()) .unwrap_or("-"); let res_name_2 = if day.is_open_shift() { let slot_second = Slot::new(Day(d), ShiftPosition::Second); let slot_second_res_id = self.get_resident_id(&slot_second); config .residents .iter() .find(|r| Some(&r.id) == slot_second_res_id) .map(|r| r.name.as_str()) } else { None }; let make_run = |text: &str| { let mut run = Run::new().add_text(text); if is_weekend { run = run.bold(); } run }; let residents_table_row = TableRow::new(vec![ TableCell::new().add_paragraph( Paragraph::new() .add_run(make_run(&format!("{d}"))) .fonts(RunFonts::new().ascii("Arial")), ), TableCell::new().add_paragraph( Paragraph::new() .add_run(make_run(weekday_to_greek( Day(d).weekday(config.month.number_from_month(), config.year), ))) .fonts(RunFonts::new().ascii("Arial")), ), TableCell::new().add_paragraph( Paragraph::new() .add_run(make_run(res_name_1)) .fonts(RunFonts::new().ascii("Arial")), ), TableCell::new().add_paragraph( Paragraph::new() .add_run(make_run(res_name_2.unwrap_or(""))) .fonts(RunFonts::new().ascii("Arial")), ), ]); residents_table = residents_table.add_row(residents_table_row); } doc = doc.add_table(residents_table); doc.build().pack(file)?; tauri_plugin_opener::open_path(path, None::<&str>) .context("Created file but failed to open it")?; Ok(()) } } #[cfg(test)] mod tests { use anyhow::Ok; use rstest::{fixture, rstest}; use crate::{ config::UserConfig, export::{Export, FileType}, resident::Resident, schedule::MonthlySchedule, scheduler::Scheduler, workload::{WorkloadBounds, WorkloadTracker}, }; #[fixture] fn schedule() -> MonthlySchedule { MonthlySchedule::new() } #[fixture] fn config() -> UserConfig { UserConfig::default().with_residents(vec![ Resident::new(1, "Στέφανος"), Resident::new(2, "Ιορδάνης"), Resident::new(3, "Μαρία"), Resident::new(4, "Βεατρίκη"), Resident::new(5, "Τάκης"), Resident::new(6, "Μάκης"), ]) } #[fixture] fn bounds(config: UserConfig) -> WorkloadBounds { WorkloadBounds::new_with_config(&config) } #[fixture] fn scheduler(config: UserConfig, bounds: WorkloadBounds) -> Scheduler { Scheduler::new(config, bounds) } #[fixture] fn tracker() -> WorkloadTracker { WorkloadTracker::default() } #[rstest] pub fn test_export_as_txt( mut schedule: MonthlySchedule, mut tracker: WorkloadTracker, scheduler: Scheduler, ) -> anyhow::Result<()> { assert!(scheduler.run(&mut schedule, &mut tracker)?); schedule.export(FileType::Txt, &scheduler.config, &tracker)?; Ok(()) } #[rstest] pub fn test_export_as_doc( mut schedule: MonthlySchedule, mut tracker: WorkloadTracker, scheduler: Scheduler, ) -> anyhow::Result<()> { assert!(scheduler.run(&mut schedule, &mut tracker)?); schedule.export(FileType::Docx, &scheduler.config, &tracker)?; Ok(()) } }