diff --git a/code/Cargo.lock b/code/Cargo.lock index 620610a..581ff92 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -770,6 +770,7 @@ dependencies = [ "egui_extras", "hex", "rust_decimal", + "rust_xlsxwriter", "sha2 0.11.0", "sqlx", "tokio", @@ -1424,6 +1425,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -3472,6 +3474,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "rust_xlsxwriter" +version = "0.95.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f281b687352597d29efaad39701d1167d5c48aa76fb973e392bc13e9d44e7f36" +dependencies = [ + "chrono", + "zip", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -4392,6 +4404,12 @@ dependencies = [ "rustc-hash 2.1.2", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typenum" version = "1.20.1" @@ -5805,12 +5823,44 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" +dependencies = [ + "crc32fast", + "flate2", + "indexmap", + "memchr", + "typed-path", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zune-core" version = "0.5.1" diff --git a/code/Cargo.toml b/code/Cargo.toml index 834a89a..912918a 100644 --- a/code/Cargo.toml +++ b/code/Cargo.toml @@ -14,6 +14,7 @@ egui_dock = "0.19.1" egui_extras = {version = "0.34.2", features = ["datepicker", "image"]} hex = "0.4.3" rust_decimal = {version = "1.42.0", features = ["macros"]} +rust_xlsxwriter = {version="0.95.0",features=["chrono"]} sha2 = "0.11.0" sqlx = {version="0.8.6", features = ["runtime-tokio", "mysql","bigdecimal","chrono"]} tokio={version = "1.52.3", features = ["full"]} diff --git a/code/src/app.rs b/code/src/app.rs index 1683f87..330a8f8 100644 --- a/code/src/app.rs +++ b/code/src/app.rs @@ -13,6 +13,8 @@ use crate::{ Equipment, LoginModalState, LoginStatus, Material, MaterialCategory, MaterialRow, ModalStatus, ModalWinState, Position, Salary, SalaryModalState, Worker, }, + recipe_tab::RecipeTabViewer, + reports::Reporter, tab::*, worker_tab::{self, *}, }; @@ -142,10 +144,11 @@ impl eframe::App for App<'_> { if self.rt.block_on(async { self.db_oper.check_admin(uname.clone()).await.unwrap() }) { - self.main_viewer.is_admin = true + self.main_viewer.is_admin = true; } else { self.main_viewer.is_admin = false; } + self.main_viewer = MainTabViewer::new(self.main_viewer.is_admin); } else { self.login_modal_state.fail = true; self.login_modal_state.fail_message = @@ -179,12 +182,14 @@ struct MainTabViewer { worker_tree: egui_dock::DockState, material_tabs: MaterialTabViewer, material_tree: egui_dock::DockState, + recipe_tab: RecipeTabViewer, rt: tokio::runtime::Runtime, equipment_modal_state: ModalWinState, salary_modal_state: SalaryModalState, is_dark_theme: bool, interface_scale_ratio: f32, is_admin: bool, + reporter: Reporter, } impl Default for MainTabViewer { fn default() -> Self { @@ -223,6 +228,7 @@ impl Default for MainTabViewer { tab_type: TabTypes::MaterialTypeList, }, ]), + recipe_tab: RecipeTabViewer::default(), rt, is_dark_theme: true, interface_scale_ratio: 1.2, @@ -251,6 +257,7 @@ impl Default for MainTabViewer { workers: HashSet::new(), }, is_admin: false, + reporter: Reporter {}, } } } @@ -261,6 +268,39 @@ impl MainTabViewer { .block_on(async { self.db_oper.get_salaries().await.unwrap() }), )); } + fn get_salary(&self) -> Salary { + Salary { + id: self + .salary_modal_state + .data + .get("id") + .unwrap() + .parse::() + .unwrap(), + worker: self + .workers + .read() + .iter() + .find(|w| { + &w.id.to_string() == self.salary_modal_state.data.get("worker_id").unwrap() + }) + .unwrap() + .clone(), + salary_size: BigDecimal::from_str( + self.salary_modal_state.data.get("salary_size").unwrap(), + ) + .unwrap(), + salary_date: chrono::NaiveDate::from_str( + self.salary_modal_state.data.get("salary_date").unwrap(), + ) + .unwrap() + .and_hms_opt(0, 0, 0) + .unwrap() + .and_local_timezone(chrono::Local) + .unwrap(), + comment: self.salary_modal_state.data.get("comment").unwrap().clone(), + } + } fn new(is_admin: bool) -> Self { Self { is_admin, @@ -531,7 +571,7 @@ impl MainTabViewer { ui.heading("Дата"); }); header.col(|ui| { - ui.heading("Описание"); + ui.heading("Примечание"); }); }) .body(|mut body| { @@ -598,10 +638,16 @@ impl MainTabViewer { }); if self.salary_modal_state.is_open { egui::Modal::new(egui::Id::new("salary_modal")).show(ui.ctx(), |ui| { - if ui.button("Закрыть").clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape)) - { - self.salary_modal_state.is_open = false; - } + ui.horizontal(|ui| { + if ui.button("Закрыть").clicked() + || ui.input(|i| i.key_pressed(egui::Key::Escape)) + { + self.salary_modal_state.is_open = false; + } + if ui.button("Отчёт").clicked() { + self.reporter.payment_report(self.get_salary().clone()); + } + }); if self.salary_modal_state.status != ModalStatus::Remove { egui::Grid::new("process_salary_grid").show(ui, |ui| { let size = ui.available_size(); @@ -810,6 +856,10 @@ impl MainTabViewer { self.interface_scale_ratio = 1.2; } } + + fn show_recipe(&mut self, ui: &mut egui::Ui) { + self.recipe_tab.show_recipe_list(ui); + } } impl egui_dock::TabViewer for MainTabViewer { @@ -835,6 +885,9 @@ impl egui_dock::TabViewer for MainTabViewer { TabTypes::Settings => { &self.show_settings(ui); } + TabTypes::Recipe => { + &self.show_recipe(ui); + } _ => { ui.label("Welcome to Avalonia!"); } diff --git a/code/src/main.rs b/code/src/main.rs index bff0232..36400d8 100644 --- a/code/src/main.rs +++ b/code/src/main.rs @@ -1,10 +1,13 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +use crate::reports::Reporter; mod app; mod database; mod material_tab; mod models; mod payment_tab; mod recipe_tab; +mod reports; mod tab; mod worker_database; mod worker_tab; diff --git a/code/src/material_tab.rs b/code/src/material_tab.rs index f2fdd52..ca3cd5f 100644 --- a/code/src/material_tab.rs +++ b/code/src/material_tab.rs @@ -41,13 +41,15 @@ impl MaterialTabViewer { } fn show_material(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { - if ui.button("Добавить").clicked() { - self.process_material_state.is_open = true; - self.process_material_state.status = ModalStatus::Add; - } - if ui.button("Удалить").clicked() { - self.process_material_state.is_open = true; - self.process_material_state.status = ModalStatus::Remove; + if self.is_admin { + if ui.button("Добавить").clicked() { + self.process_material_state.is_open = true; + self.process_material_state.status = ModalStatus::Add; + } + // if ui.button("Удалить").clicked() { + // self.process_material_state.is_open = true; + // self.process_material_state.status = ModalStatus::Remove; + // } } if ui.button("Обновить").clicked() { self.update_materials(); @@ -352,15 +354,13 @@ impl MaterialTabViewer { } } - fn show_material_type(&mut self, ui: &mut egui::Ui) { + fn show_material_category(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { - if ui.button("Добавить").clicked() { - self.process_mcat_state.is_open = true; - self.process_mcat_state.status = ModalStatus::Add; - } - if ui.button("Удалить").clicked() { - self.process_mcat_state.is_open = true; - self.process_mcat_state.status = ModalStatus::Remove; + if self.is_admin { + if ui.button("Добавить").clicked() { + self.process_mcat_state.is_open = true; + self.process_mcat_state.status = ModalStatus::Add; + } } if ui.button("Обновить").clicked() { self.update_material_category(); @@ -526,7 +526,7 @@ impl egui_dock::TabViewer for MaterialTabViewer { fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { match &tab.tab_type { TabTypes::MaterialList => self.show_material(ui), - TabTypes::MaterialTypeList => self.show_material_type(ui), + TabTypes::MaterialTypeList => self.show_material_category(ui), _ => { ui.label("Каким образом?"); } diff --git a/code/src/models.rs b/code/src/models.rs index 79b16ce..2ad1322 100644 --- a/code/src/models.rs +++ b/code/src/models.rs @@ -45,7 +45,7 @@ pub struct SalaryRow { pub salary_date: chrono::DateTime, pub comment: String, } - +#[derive(Clone)] pub struct Salary { pub id: i32, pub worker: Worker, diff --git a/code/src/recipe_tab.rs b/code/src/recipe_tab.rs index 286132b..ce19908 100644 --- a/code/src/recipe_tab.rs +++ b/code/src/recipe_tab.rs @@ -1,19 +1,33 @@ -use crate::tab::{Tab, TabTypes}; -use std::default::Default; +use tokio::sync::RwLock; -struct RecipeTabViewer { +use crate::{ + database::DBOperator, + models::{Equipment, Material, Recipe, RecipeElement}, + tab::{Tab, TabTypes}, +}; +use std::{default::Default, sync::Arc}; + +pub struct RecipeTabViewer { is_admin: bool, + rt: tokio::runtime::Runtime, + db_oper: DBOperator, + + recipes: Arc>>, + recipe_elements: Arc>>, + materials: Arc>>, + equipment: Arc>>, } impl RecipeTabViewer { - fn new(is_admin: bool) -> Self { + pub fn new(is_admin: bool) -> Self { Self { is_admin, ..Default::default() } } - fn show_recipe_list(&mut self, ui: &mut egui::Ui) {} - fn show_recipe_detail(&mut self, ui: &mut egui::Ui) {} + pub fn show_recipe_list(&mut self, ui: &mut egui::Ui) { + ui.label("Goida"); + } } impl egui_dock::TabViewer for RecipeTabViewer { type Tab = Tab; @@ -27,9 +41,6 @@ impl egui_dock::TabViewer for RecipeTabViewer { TabTypes::RecipeList => { self.show_recipe_list(ui); } - TabTypes::RecipeDetail => { - self.show_recipe_detail(ui); - } _ => { ui.label("Помогите!!"); } diff --git a/code/src/reports.rs b/code/src/reports.rs new file mode 100644 index 0000000..c9dd213 --- /dev/null +++ b/code/src/reports.rs @@ -0,0 +1,40 @@ +use bigdecimal::ToPrimitive; +use rust_xlsxwriter::{Format, Workbook, XlsxError}; + +use crate::models::Salary; + +pub struct Reporter {} +impl Reporter { + pub fn test_report(&self) -> Result<(), XlsxError> { + let mut workbook = Workbook::new(); + + let mut worksheet = workbook.add_worksheet(); + + worksheet.write_string(0, 0, "ZOV"); + worksheet.write_formula(0, 1, "=RAND()"); + + workbook.save("test.xlsx"); + Ok(()) + } + pub fn payment_report(&self, sal: Salary) -> Result<(), XlsxError> { + let mut wb = Workbook::new(); + let mut ws = wb.add_worksheet(); + let money_format = Format::new().set_num_format("#,##0.00 ₽"); + ws.write_string(0, 0, "ФИО"); + ws.write_string(0, 1, "Размер оплаты"); + ws.write_string(0, 2, "Дата оплаты"); + ws.write_string(0, 3, "Комментарий"); + + ws.write_string(1, 0, &sal.worker.full_name); + ws.write_with_format(1, 1, sal.salary_size.to_f64().unwrap(), &money_format); + ws.write_datetime(1, 2, sal.salary_date.date_naive()); + ws.write_string(1, 3, sal.comment); + ws.autofit(); + + wb.save(format!( + "report-worker-{}.xlsx", + chrono::Local::now().to_string() + )); + Ok(()) + } +}