diff --git a/code/src/app.rs b/code/src/app.rs index e71b48a..72edea5 100644 --- a/code/src/app.rs +++ b/code/src/app.rs @@ -8,12 +8,13 @@ use std::{ use crate::{ database::DBOperator, material_tab::MaterialTabViewer, - misc, + misc::{self, ELEMENTS_PER_PAGE}, models::{ Equipment, EquipmentFilter, EquipmentModalState, LoginModalState, LoginStatus, Material, MaterialCategory, MaterialRow, ModalStatus, ModalWinState, Position, Salary, SalaryModalState, Worker, }, + order_tab::OrderTabViewer, recipe_tab::RecipeTabViewer, reports::Reporter, tab::*, @@ -22,7 +23,7 @@ use crate::{ use bigdecimal::ToPrimitive; use chrono::{Datelike, NaiveDate}; use egui::{TextBuffer, Vec2, mutex::RwLock}; -use egui_dock::{DockArea, Style, TabViewer}; +use egui_dock::{DockArea, DockState, Style, TabViewer}; use egui_extras::{Column, TableBuilder}; use sha2::{Digest, Sha256}; use sqlx::types::BigDecimal; @@ -192,17 +193,21 @@ struct MainTabViewer { material_tabs: MaterialTabViewer, material_tree: egui_dock::DockState, recipe_tab: RecipeTabViewer, + order_tabs: OrderTabViewer, + order_tree: egui_dock::DockState, equipment_modal_state: EquipmentModalState, salary_modal_state: SalaryModalState, is_dark_theme: bool, interface_scale_ratio: f32, - page: i32, - elements_per_page: i32, + equipment_page: i32, + salaries_page: i32, + workers_page: i32, is_admin: bool, reporter: Reporter, + only_hired_workers: bool, } impl Default for MainTabViewer { fn default() -> Self { @@ -210,15 +215,21 @@ impl Default for MainTabViewer { let db_oper = rt.block_on(async { DBOperator::new().await }); let elements_per_page = 5; Self { - workers: Arc::new(RwLock::new( - rt.block_on(async { db_oper.get_workers().await.unwrap() }), - )), - equipment: Arc::new(RwLock::new(rt.block_on(async { - db_oper.get_equipment(0, elements_per_page).await.unwrap() + workers: Arc::new(RwLock::new(rt.block_on(async { + db_oper + .get_workers(0, elements_per_page, false) + .await + .unwrap() + }))), + equipment: Arc::new(RwLock::new(rt.block_on(async { + db_oper + .get_equipment(0, elements_per_page, false) + .await + .unwrap() + }))), + salaries: Arc::new(RwLock::new(rt.block_on(async { + db_oper.get_salaries(0, elements_per_page).await.unwrap() }))), - salaries: Arc::new(RwLock::new( - rt.block_on(async { db_oper.get_salaries().await.unwrap() }), - )), db_oper, worker_tabs: WorkerTabViewer::default(), worker_tree: egui_dock::DockState::new(vec![ @@ -247,6 +258,18 @@ impl Default for MainTabViewer { }, ]), recipe_tab: RecipeTabViewer::default(), + + order_tabs: OrderTabViewer::default(), + order_tree: DockState::new(vec![ + Tab { + title: "Входящие".to_owned(), + tab_type: TabTypes::IncomingOrderList, + }, + Tab { + title: "Исходящие".to_owned(), + tab_type: TabTypes::OutcomingOrderList, + }, + ]), rt, is_dark_theme: true, interface_scale_ratio: 1.2, @@ -273,22 +296,45 @@ impl Default for MainTabViewer { is_admin: false, reporter: Reporter {}, - elements_per_page, - page: 0, + equipment_page: 0, + salaries_page: 0, + workers_page: 0, + only_hired_workers: false, } } } impl MainTabViewer { fn update_salary(&mut self) { - self.salaries = Arc::new(RwLock::new( - self.rt - .block_on(async { self.db_oper.get_salaries().await.unwrap() }), - )); + self.salaries = Arc::new(RwLock::new(self.rt.block_on(async { + self.db_oper + .get_salaries( + self.salaries_page * misc::ELEMENTS_PER_PAGE, + misc::ELEMENTS_PER_PAGE, + ) + .await + .unwrap() + }))); } fn update_equipment(&mut self) { self.equipment = Arc::new(RwLock::new(self.rt.block_on(async { self.db_oper - .get_equipment(self.page, self.elements_per_page) + .get_equipment( + self.equipment_page * misc::ELEMENTS_PER_PAGE, + misc::ELEMENTS_PER_PAGE, + self.equipment_modal_state.filters.without_written_offs, + ) + .await + .unwrap() + }))); + } + fn update_workers(&mut self) { + self.workers = Arc::new(RwLock::new(self.rt.block_on(async { + self.db_oper + .get_workers( + self.workers_page * misc::ELEMENTS_PER_PAGE, + ELEMENTS_PER_PAGE, + self.only_hired_workers, + ) .await .unwrap() }))); @@ -345,10 +391,31 @@ impl MainTabViewer { } }); } - ui.add(egui::Checkbox::new( - &mut self.equipment_modal_state.filters.without_written_offs, - "Без списанных", - )); + ui.horizontal(|ui| { + if ui + .add(egui::Checkbox::new( + &mut self.equipment_modal_state.filters.without_written_offs, + "Без списанных", + )) + .changed() + { + self.update_equipment(); + }; + if ui.button("<-").clicked() { + if self.equipment_page > 0 { + self.equipment_page -= 1; + } + self.update_equipment(); + } + ui.monospace(self.equipment_page.to_string()); + if ui.button("->").clicked() { + if self.equipment.read().len() == 5 { + self.equipment_page += 1; + self.update_equipment(); + } + } + }); + TableBuilder::new(ui) .striped(true) .resizable(false) @@ -381,47 +448,42 @@ impl MainTabViewer { .body(|mut body| { body.ui_mut().style_mut().interaction.selectable_labels = false; for eq in self.equipment.read().clone().iter() { - if (self.equipment_modal_state.filters.without_written_offs - && !eq.is_written_off) - || !self.equipment_modal_state.filters.without_written_offs - { - body.row(20.0, |mut row| { - row.col(|ui| { - ui.label(&eq.name); - }); - row.col(|ui| { - ui.label(&eq.inv_number); - }); - row.col(|ui| { - ui.label(&eq.worker.full_name); - }); - row.col(|ui| { - ui.label(eq.maintenance_date.date_naive().to_string()); - }); - row.col(|ui| { - ui.label(if eq.is_written_off { "Да" } else { "Нет" }); - }); - row.col(|ui| { - if self.is_admin { - if ui.button("Списать").clicked() { - self.equipment_modal_state.is_open = true; - self.equipment_modal_state.status = ModalStatus::Remove; - self.equipment_modal_state.equipment = eq.clone(); - } + body.row(20.0, |mut row| { + row.col(|ui| { + ui.label(&eq.name); + }); + row.col(|ui| { + ui.label(&eq.inv_number); + }); + row.col(|ui| { + ui.label(&eq.worker.full_name); + }); + row.col(|ui| { + ui.label(eq.maintenance_date.date_naive().to_string()); + }); + row.col(|ui| { + ui.label(if eq.is_written_off { "Да" } else { "Нет" }); + }); + row.col(|ui| { + if self.is_admin { + if ui.button("Списать").clicked() { + self.equipment_modal_state.is_open = true; + self.equipment_modal_state.status = ModalStatus::Remove; + self.equipment_modal_state.equipment = eq.clone(); } - }); - if row.response().double_clicked() { - self.equipment_modal_state.is_open = true; - self.equipment_modal_state.status = ModalStatus::Edit; - self.equipment_modal_state.equipment = eq.clone(); } }); - } + if row.response().double_clicked() { + self.equipment_modal_state.is_open = true; + self.equipment_modal_state.status = ModalStatus::Edit; + self.equipment_modal_state.equipment = eq.clone(); + } + }); } }); + if self.equipment_modal_state.is_open { egui::Modal::new(egui::Id::new("process_equipment_modal")).show(ui.ctx(), |ui| { - let mut date_okay = false; if ui.button("Закрыть").clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape)) { self.equipment_modal_state.is_open = false; @@ -449,6 +511,8 @@ impl MainTabViewer { ui.label("Ответственный"); egui::ComboBox::new("process_equipment_combobox", "") + .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) + .height(100.0) .selected_text( self.equipment_modal_state .equipment @@ -464,6 +528,23 @@ impl MainTabViewer { &wkrk.full_name, ); } + ui.horizontal(|ui| { + if ui.button("<-").clicked() { + if self.workers_page > 0 { + self.workers_page -= 1; + self.update_workers(); + }; + } + ui.monospace(self.workers_page.to_string()); + if ui.button("->").clicked() { + if self.workers.read().len().to_i32().unwrap() + == misc::ELEMENTS_PER_PAGE + { + self.workers_page += 1; + self.update_workers(); + } + } + }) }); ui.end_row(); @@ -561,6 +642,12 @@ impl MainTabViewer { .id(id) .show_inside(ui, &mut self.material_tabs); } + fn show_order(&mut self, ui: &mut egui::Ui) { + let id = ui.make_persistent_id("OrderMenu"); + DockArea::new(&mut self.order_tree) + .id(id) + .show_inside(ui, &mut self.order_tabs); + } fn show_salary(&mut self, ui: &mut egui::Ui) { /* * Чё надо: @@ -920,6 +1007,9 @@ impl egui_dock::TabViewer for MainTabViewer { TabTypes::Recipe => { &self.show_recipe(ui); } + TabTypes::Order => { + &self.show_order(ui); + } _ => { ui.label("Welcome to Avalonia!"); } diff --git a/code/src/database.rs b/code/src/database.rs index bb89f19..b9cd0d0 100644 --- a/code/src/database.rs +++ b/code/src/database.rs @@ -35,10 +35,22 @@ impl DBOperator { .fetch_one(&self.pool) .await } - pub async fn get_workers(&self) -> Result, sqlx::Error> { - let pre_rets: Vec = sqlx::query_as::<_, WorkerRow>("SELECT * FROM `worker`") - .fetch_all(&self.pool) - .await?; + pub async fn get_workers( + &self, + start: i32, + length: i32, + only_not_fired: bool, + ) -> Result, sqlx::Error> { + let pre_rets: Vec = match only_not_fired { + true => sqlx::query_as::<_, WorkerRow>( + "SELECT * FROM `worker` WHERE is_fired=0 LIMIT ? OFFSET ?", + ), + false => sqlx::query_as::<_, WorkerRow>("SELECT * FROM `worker` LIMIT ? OFFSET ?"), + } + .bind(length) + .bind(start) + .fetch_all(&self.pool) + .await?; let mut rets: Vec = Vec::new(); let pos = self.get_position().await.unwrap(); for worker in pre_rets { @@ -84,23 +96,33 @@ impl DBOperator { &self, start: i32, count: i32, + without_written_offs: bool, ) -> Result, sqlx::Error> { - let pre_rets = - sqlx::query_as::<_, EquipmentRow>("SELECT * FROM `equipment` LIMIT ? OFFSET ?") + let pre_rets = match without_written_offs { + true => { + sqlx::query_as::<_, EquipmentRow>( + "SELECT * FROM `equipment` WHERE is_written_off=0 LIMIT ? OFFSET ? ", + ) .bind(count) .bind(start) .fetch_all(&self.pool) - .await?; - let mut rets: Vec = Vec::new(); - let workers = self.get_workers().await?; - for eq in pre_rets { - let mut pworker = Worker::default(); - for worker in workers.clone() { - if worker.id == eq.worker_id { - pworker = worker.clone(); - break; - } + .await? } + false => { + sqlx::query_as::<_, EquipmentRow>("SELECT * FROM `equipment` LIMIT ? OFFSET ?") + .bind(count) + .bind(start) + .fetch_all(&self.pool) + .await? + } + }; + + let mut rets: Vec = Vec::new(); + for eq in pre_rets { + let pworker = self + .get_worker_by_id(eq.worker_id) + .await + .unwrap_or(Worker::default()); rets.push(Equipment { id: eq.id, name: eq.name, @@ -186,26 +208,57 @@ impl DBOperator { } Ok(rets) } + pub async fn get_promotions( + &self, + start: i32, + length: i32, + ) -> Result, sqlx::Error> { + let prets = sqlx::query_as::<_, PromotionRow>("SELECT * FROM `promotion` LIMIT ? OFFSET ?") + .bind(length) + .bind(start) + .fetch_all(&self.pool) + .await?; + let mut rets: Vec = Vec::new(); + for p in prets { + rets.push(Promotion { + id: p.id, + worker: self + .get_worker_by_id(p.worker_id) + .await + .unwrap_or(Worker::default()), + from_position: self + .get_position_by_id(p.from_position_id) + .await + .unwrap_or(Position::default()), + to_position: self + .get_position_by_id(p.to_position_id) + .await + .unwrap_or(Position::default()), + date: p.date, + }); + } + Ok(rets) + } pub async fn promote_worker( &self, worker: Worker, - position_to: Position, + position_from: Position, date: DateTime, ) -> Result<(), sqlx::Error> { let mut tx = self.pool.begin().await?; - sqlx::query("INSERT INTO promotion (worker_id, from_position_id, to_position_id, `date`) VALUES(?, ?, ?, ?);").bind(worker.id).bind(worker.position.id).bind(position_to.id).bind(date).execute(&mut *tx).await?; + sqlx::query("INSERT INTO promotion (worker_id, from_position_id, to_position_id, `date`) VALUES(?, ?, ?, ?);").bind(worker.id).bind(position_from.id).bind(worker.position.id).bind(date).execute(&mut *tx).await?; sqlx::query("UPDATE worker SET position_id=? WHERE id=?;") - .bind(position_to.id) + .bind(worker.position.id) .bind(worker.id) .execute(&mut *tx) .await?; tx.commit().await } - pub async fn add_worker(&self, worker: WorkerRow) -> Result { + pub async fn add_worker(&self, worker: Worker) -> Result { // sqlx::query!("INSERT INTO Brewery.worker (id, position_id, hire_date, is_fired, full_name) VALUES(0, ?, ?, ?, ?);",worker.position.id, worker.hire_date ,worker.is_fired,worker.full_name) match sqlx::query("INSERT INTO `worker` (id, position_id, hire_date, is_fired, full_name) VALUES(0, ?, ?, ?, ?);") - .bind(worker.position_id) + .bind(worker.position.id) .bind(worker.hire_date) .bind(worker.is_fired) .bind(worker.full_name) @@ -291,17 +344,14 @@ impl DBOperator { } } - pub async fn update_worker(&self, worker: WorkerRow) -> Result { - match sqlx::query( - "UPDATE `worker` SET position_id=?, hire_date=?, is_fired=?, full_name=? WHERE id=?;", - ) - .bind(worker.position_id) - .bind(worker.hire_date) - .bind(worker.is_fired) - .bind(worker.full_name) - .bind(worker.id) - .execute(&self.pool) - .await + pub async fn update_worker(&self, worker: Worker) -> Result { + match sqlx::query("UPDATE `worker` SET hire_date=?, is_fired=?, full_name=? WHERE id=?;") + .bind(worker.hire_date) + .bind(worker.is_fired) + .bind(worker.full_name) + .bind(worker.id) + .execute(&self.pool) + .await { Ok(_) => Ok(true), Err(_) => Ok(false), @@ -401,14 +451,15 @@ impl DBOperator { Err(_) => Ok(false), } } - pub async fn get_salaries(&self) -> Result, sqlx::Error> { - let sals = sqlx::query_as::<_, SalaryRow>("SELECT * FROM `salary`") + pub async fn get_salaries(&self, start: i32, length: i32) -> Result, sqlx::Error> { + let sals = sqlx::query_as::<_, SalaryRow>("SELECT * FROM `salary` LIMIT ? OFFSET ?") + .bind(length) + .bind(start) .fetch_all(&self.pool) .await?; let mut rets: Vec = Vec::new(); - let workers = self.get_workers().await.unwrap(); for sal in sals { - match workers.iter().find(|w| w.id == sal.worker_id) { + match self.get_worker_by_id(sal.worker_id).await.ok() { Some(w) => rets.push(Salary { id: sal.id, worker: w.clone(), @@ -466,6 +517,25 @@ impl DBOperator { Err(_) => Ok(false), } } + pub async fn get_recipe_by_id(&self, id: i32) -> Recipe { + let pret = sqlx::query_as::<_, RecipeRow>("SELECT * FROM `recipe` WHERE id = ?") + .bind(id) + .fetch_one(&self.pool) + .await + .unwrap(); + let pelems = self.get_recipe_elements().await.unwrap_or(Vec::new()); + let mut elems: HashSet = HashSet::new(); + for pr in pelems { + if pr.recipe_id == pret.id { + elems.insert(pr.clone()); + } + } + Recipe { + id: pret.id, + name: pret.name.clone(), + elements: elems, + } + } pub async fn get_recipes(&self) -> Result, sqlx::Error> { let pre_ret = sqlx::query_as::<_, RecipeRow>("SELECT * FROM `recipe`") .fetch_all(&self.pool) @@ -582,6 +652,78 @@ impl DBOperator { Err(_) => Ok(false), } } + pub async fn get_orders( + &self, + start: i32, + length: i32, + is_incoming: bool, + ) -> Vec{ + let pret = sqlx::query_as::<_, OrderRow>( + "SELECT * FROM `order` WHERE is_incoming=? LIMIT ? OFFSET ?", + ) + .bind(is_incoming) + .bind(length) + .bind(start) + .fetch_all(&self.pool) + .await + .unwrap(); + + let mut ret: Vec = Vec::new(); + for or in pret { + ret.push(Order { + id: or.id, + order_date: or.order_date, + client: self.get_client_by_id(or.client_id).await.unwrap(), + elements: self.get_order_elements_by_order_id(or.id).await, + total_price: or.total_price, + }) + } + ret + } + pub async fn get_order_elements_by_order_id(&self, order_id: i32) -> Vec { + let pret = sqlx::query_as::<_, OrderElementRow>( + "SELECT * FROM `order_element` WHERE order_id = ?", + ) + .bind(order_id) + .fetch_all(&self.pool) + .await + .unwrap(); + let mut ret: Vec = Vec::new(); + for or in pret { + ret.push(OrderElement { + id: or.id, + total_price: or.total_price, + product: self.get_product_by_id(or.product_id).await, + quantity: or.quantity, + }); + } + ret + } + pub async fn get_client_by_id(&self, id: i32) -> Result { + sqlx::query_as::<_, Client>("SELECT FROM `client` WHERE id=?") + .bind(id) + .fetch_one(&self.pool) + .await + } + pub async fn get_product_by_id(&self, id: i32) -> Product { + let pret = sqlx::query_as::<_, ProductRow>("SELECT * FROM `product` WHERE id = ?") + .bind(id) + .fetch_one(&self.pool) + .await + .unwrap(); + let mut recipe: Recipe = Recipe::default(); + for rec in self.get_recipes().await.unwrap() { + if rec.id == pret.recipe_id { + recipe = rec.clone(); + } + } + Product { + id: pret.id, + recipe, + price_per_unit: pret.price_per_unit, + volume: pret.volume, + } + } } pub async fn estabilish_connection() -> Result<(), sqlx::Error> { diff --git a/code/src/main.rs b/code/src/main.rs index 1ed6e06..45d0fa6 100644 --- a/code/src/main.rs +++ b/code/src/main.rs @@ -4,7 +4,7 @@ mod app; mod database; mod material_tab; mod models; -mod production_tab; +mod order_tab; mod recipe_tab; mod reports; mod tab; @@ -16,8 +16,8 @@ fn main() { database::estabilish_connection(); let mut native_opts = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() - .with_inner_size([815.0, 300.0]) - .with_min_inner_size([750.0, 200.0]), + .with_inner_size([815.0, 400.0]) + .with_min_inner_size([750.0, 350.0]), ..Default::default() }; native_opts.renderer = eframe::Renderer::Glow; diff --git a/code/src/misc.rs b/code/src/misc.rs index 396e0f7..45803ba 100644 --- a/code/src/misc.rs +++ b/code/src/misc.rs @@ -3,6 +3,8 @@ use std::str::FromStr; use bigdecimal::ToPrimitive; use chrono::Datelike; +pub const ELEMENTS_PER_PAGE: i32 = 5; + pub fn naivedate_to_jiff(date: chrono::NaiveDate) -> jiff::civil::Date { jiff::civil::Date::new( date.year().to_i16().unwrap(), diff --git a/code/src/models.rs b/code/src/models.rs index 6aa6040..33c62ef 100644 --- a/code/src/models.rs +++ b/code/src/models.rs @@ -121,29 +121,53 @@ pub struct RecipeRow { pub id: i32, pub name: String, } +#[derive(FromRow)] pub struct Client { - id: i32, - name: String, - address: String, + pub id: i32, + pub name: String, + pub address: String, } -pub struct Order<'a> { - id: i32, - client: Client, - order_date: chrono::DateTime, - total_amount: rust_decimal::Decimal, - elements: std::vec::Vec>, +pub struct Order { + pub id: i32, + pub client: Client, + pub order_date: chrono::DateTime, + pub total_price: BigDecimal, + pub elements: std::vec::Vec, } -pub struct OrderElement<'a> { - id: i32, - product: Product<'a>, - quantity: i32, - total_amount: rust_decimal::Decimal, +#[derive(FromRow)] +pub struct OrderRow { + pub id: i32, + pub client_id: i32, + pub order_date: chrono::DateTime, + pub total_price: BigDecimal, } -pub struct Product<'a> { - id: i32, - recipe: &'a Recipe, - volume: f64, - price_per_unit: rust_decimal::Decimal, +pub struct OrderElement { + pub id: i32, + pub product: Product, + pub quantity: i32, + pub total_price: BigDecimal, +} +#[derive(FromRow)] +pub struct OrderElementRow { + pub id: i32, + pub product_id: i32, + pub quantity: i32, + pub total_price: BigDecimal, + pub is_incoming: bool, +} + +pub struct Product { + pub id: i32, + pub recipe: Recipe, + pub volume: f64, + pub price_per_unit: BigDecimal, +} +#[derive(FromRow)] +pub struct ProductRow { + pub id: i32, + pub recipe_id: i32, + pub volume: f64, + pub price_per_unit: BigDecimal, } #[derive(Default)] pub struct User { @@ -151,11 +175,22 @@ pub struct User { is_admin: bool, worker: Worker, } +#[derive(Default)] pub struct Promotion { + pub id: i32, pub worker: Worker, + pub from_position: Position, pub to_position: Position, pub date: DateTime, } +#[derive(FromRow)] +pub struct PromotionRow { + pub id: i32, + pub worker_id: i32, + pub from_position_id: i32, + pub to_position_id: i32, + pub date: DateTime, +} #[derive(Default)] pub struct EquipmentModalState { @@ -165,6 +200,14 @@ pub struct EquipmentModalState { pub status: ModalStatus, pub filters: EquipmentFilter, } +#[derive(Default)] +pub struct WorkerModalState { + pub is_open: bool, + pub can_finish: bool, + pub worker: Worker, + pub status: ModalStatus, + pub old_pos: Position, +} #[derive(Default)] pub struct ModalWinState { diff --git a/code/src/production_tab.rs b/code/src/production_tab.rs deleted file mode 100644 index ee5e12f..0000000 --- a/code/src/production_tab.rs +++ /dev/null @@ -1 +0,0 @@ -struct ProductionTabViewer {} diff --git a/code/src/recipe_tab.rs b/code/src/recipe_tab.rs index 463ffdb..053f1be 100644 --- a/code/src/recipe_tab.rs +++ b/code/src/recipe_tab.rs @@ -302,7 +302,7 @@ impl Default for RecipeTabViewer { rt.block_on(async { db_oper.get_materials().await.unwrap_or(Vec::new()) }), )), equipment: Arc::new(RwLock::new( - rt.block_on(async { db_oper.get_equipment(0,5).await.unwrap_or(Vec::new()) }), + rt.block_on(async { db_oper.get_equipment(0,5, false).await.unwrap_or(Vec::new()) }), )), process_recipe_status: RecipeModalState { diff --git a/code/src/tab.rs b/code/src/tab.rs index cfaf848..99434d4 100644 --- a/code/src/tab.rs +++ b/code/src/tab.rs @@ -18,6 +18,8 @@ pub enum TabTypes { RecipeList, RecipeDetail, ProductList, + IncomingOrderList, + OutcomingOrderList, } pub struct Tab { diff --git a/code/src/worker_tab.rs b/code/src/worker_tab.rs index bb2484b..6db883d 100644 --- a/code/src/worker_tab.rs +++ b/code/src/worker_tab.rs @@ -6,20 +6,26 @@ use std::default::Default; use std::str::FromStr; use std::sync::Arc; - -use crate::database::*; -use crate::models::{ModalStatus, ModalWinState, Position, Worker, WorkerRow}; +use crate::misc::ELEMENTS_PER_PAGE; +use crate::models::{ + ModalStatus, ModalWinState, Position, Promotion, Worker, WorkerModalState, WorkerRow, +}; use crate::tab::*; +use crate::{database::*, misc}; pub struct WorkerTabViewer { db_oper: DBOperator, workers: Arc>>, positions: Arc>>, + promotions: Arc>>, rt: tokio::runtime::Runtime, - process_worker_state: ModalWinState, + process_worker_state: WorkerModalState, process_position_state: ModalWinState, is_admin: bool, + + only_not_fired_workers: bool, + workers_page: i32, } impl WorkerTabViewer { @@ -30,10 +36,16 @@ impl WorkerTabViewer { } } fn update_workers(&mut self) { - self.workers = Arc::new(RwLock::new( - self.rt - .block_on(async { self.db_oper.get_workers().await.unwrap() }), - )); + self.workers = Arc::new(RwLock::new(self.rt.block_on(async { + self.db_oper + .get_workers( + self.workers_page, + ELEMENTS_PER_PAGE, + self.only_not_fired_workers, + ) + .await + .unwrap() + }))); } fn update_positions(&mut self) { self.positions = Arc::new(RwLock::new( @@ -46,28 +58,14 @@ impl WorkerTabViewer { ui.horizontal(|ui| { if ui.button("Добавить").clicked() { self.process_worker_state.is_open = true; - self.process_worker_state - .data - .insert("id".to_string(), "0".to_string()); - self.process_worker_state - .data - .insert("full_name".to_string(), "".to_string()); - self.process_worker_state.data.insert( - "position_id".to_string(), - self.positions.read()[0].name.clone(), - ); - self.process_worker_state - .data - .insert("is_fired".to_string(), false.to_string()); - self.process_worker_state - .data - .insert("hire_date".to_string(), "".to_string()); + self.process_worker_state.worker = Worker::default(); }; if ui.button("Обновить").clicked() { self.update_workers(); } }); if self.process_worker_state.is_open { + let pos = self.process_worker_state.worker.position.clone(); egui::Modal::new(egui::Id::new("process_worker_modal")).show(ui.ctx(), |ui| { if ui.button("Закрыть").clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape)) { @@ -78,34 +76,25 @@ impl WorkerTabViewer { ui.label("Имя:"); ui.add( egui::TextEdit::singleline( - self.process_worker_state.data.get_mut("full_name").unwrap(), + &mut self.process_worker_state.worker.full_name, ) .hint_text("Иванов Иван Иванович"), ); ui.end_row(); ui.label("Должность:"); - let posname = self - .positions - .read() - .clone() - .iter() - .find(|p| { - p.id.to_string() - == *self.process_worker_state.data.get("position_id").unwrap() - }) - .map(|m| m.name.clone()); egui::ComboBox::new("process_worker_combobox", "") - .selected_text(posname.unwrap_or("Выберите".to_string())) + .selected_text(if self.process_worker_state.worker.position.id == 0 { + "Выберите:" + } else { + &self.process_worker_state.worker.position.name + }) .show_ui(ui, |ui| { for pos in self.positions.read().clone().iter() { ui.selectable_value( - self.process_worker_state - .data - .get_mut("position_id") - .unwrap(), - pos.id.to_string(), - pos.name.clone(), + &mut self.process_worker_state.worker.position, + pos.clone(), + &pos.name, ); } }); @@ -114,34 +103,31 @@ impl WorkerTabViewer { let mut size = ui.spacing().interact_size; size.x = 200.0; ui.label("Дата найма:"); - ui.add_sized( - size, - egui::TextEdit::singleline( - self.process_worker_state.data.get_mut("hire_date").unwrap(), - ) - .hint_text("1970-01-23"), - ); + let ndate = self.process_worker_state.worker.hire_date.date_naive(); + let mut date = misc::naivedate_to_jiff(ndate); + ui.add(egui_extras::DatePickerButton::new(&mut date)); + if misc::naivedate_to_jiff( + self.process_worker_state.worker.hire_date.date_naive(), + ) != date + { + self.process_worker_state.worker.hire_date = + misc::jiff_to_naivedate(date) + .and_hms_opt(0, 0, 0) + .unwrap() + .and_local_timezone(chrono::Local) + .unwrap(); + } + ui.end_row(); if self.process_worker_state.status == ModalStatus::Edit { ui.label("Уволен:"); - let data = self.process_worker_state.data.get("is_fired").unwrap(); - let mut checked = data.parse::().unwrap(); - ui.checkbox(&mut checked, ""); - if checked.to_string() != data.clone() { - self.process_worker_state - .data - .insert("is_fired".to_string(), checked.to_string()); - } + ui.checkbox(&mut self.process_worker_state.worker.is_fired, ""); } }); - self.process_worker_state.can_finish = self.check_date( - self.process_worker_state - .data - .get("hire_date") - .unwrap() - .clone(), - ); + self.process_worker_state.can_finish = + self.process_worker_state.worker.full_name.len() > 0 + && self.process_worker_state.worker.position.id != 0; } let btext = match self.process_worker_state.status { ModalStatus::Add => "Добавить", @@ -160,91 +146,27 @@ impl WorkerTabViewer { ModalStatus::Add => { self.rt.block_on(async { self.db_oper - .add_worker(WorkerRow { - id: self - .process_worker_state - .data - .get("id") - .unwrap() - .parse::() - .unwrap(), - full_name: self - .process_worker_state - .data - .get("full_name") - .unwrap() - .clone(), - position_id: self - .process_worker_state - .data - .get("position_id") - .unwrap() - .parse::() - .unwrap(), - hire_date: chrono::NaiveDate::parse_from_str( - &self - .process_worker_state - .data - .get("hire_date") - .unwrap(), - "%Y-%m-%d", - ) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap() - .and_local_timezone(chrono::Local) - .unwrap(), - is_fired: false, - }) + .add_worker(self.process_worker_state.worker.clone()) .await - .ok() + .ok(); }); } ModalStatus::Edit => { self.rt.block_on(async { - self.db_oper - .update_worker(WorkerRow { - id: self - .process_worker_state - .data - .get("id") - .unwrap() - .parse::() - .unwrap(), - full_name: self - .process_worker_state - .data - .get("full_name") - .unwrap() - .clone(), - position_id: self - .process_worker_state - .data - .get("position_id") - .unwrap() - .parse::() - .unwrap(), - is_fired: self - .process_worker_state - .data - .get("is_fired") - .unwrap() - .parse::() - .unwrap(), - hire_date: chrono::NaiveDate::parse_from_str( - &self - .process_worker_state - .data - .get("hire_date") - .unwrap(), - "%Y-%m-%d", + if self.process_worker_state.worker.position.id + != self.process_worker_state.old_pos.id + { + self.db_oper + .promote_worker( + self.process_worker_state.worker.clone(), + self.process_worker_state.old_pos.clone(), + chrono::Local::now(), ) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap() - .and_local_timezone(chrono::Local) - .unwrap(), - }) + .await + .unwrap(); + } + self.db_oper + .update_worker(self.process_worker_state.worker.clone()) .await .ok(); }); @@ -294,25 +216,11 @@ impl WorkerTabViewer { row.col(|ui| { ui.label(if wk.is_fired { "Да" } else { "Нет" }); }); - if row.response().clicked() { + if row.response().double_clicked() { self.process_worker_state.is_open = true; self.process_worker_state.status = ModalStatus::Edit; - self.process_worker_state - .data - .insert("id".to_string(), wk.id.to_string()); - self.process_worker_state - .data - .insert("full_name".to_string(), wk.full_name.clone()); - self.process_worker_state.data.insert( - "hire_date".to_string(), - wk.hire_date.date_naive().to_string(), - ); - self.process_worker_state - .data - .insert("is_fired".to_string(), wk.is_fired.to_string()); - self.process_worker_state - .data - .insert("position_id".to_string(), wk.position.id.to_string()); + self.process_worker_state.worker = wk.clone(); + self.process_worker_state.old_pos = wk.position.clone(); } }); } @@ -507,6 +415,44 @@ impl WorkerTabViewer { }); } } + fn show_promotions(&self, ui: &mut egui::Ui) { + egui_extras::TableBuilder::new(ui) + .columns(Column::auto(), 4) + .striped(true) + .vscroll(false) + .header(25.0, |mut header| { + header.col(|ui| { + ui.heading("Работник"); + }); + header.col(|ui| { + ui.heading("Из"); + }); + header.col(|ui| { + ui.heading("В"); + }); + header.col(|ui| { + ui.heading("Дата"); + }); + }) + .body(|mut body| { + for pr in self.promotions.read().iter().clone() { + body.row(25.0, |mut row| { + row.col(|ui| { + ui.label(&pr.worker.full_name); + }); + row.col(|ui| { + ui.label(&pr.from_position.name); + }); + row.col(|ui| { + ui.label(&pr.to_position.name); + }); + row.col(|ui| { + ui.label(pr.date.date_naive().to_string()); + }); + }); + } + }); + } fn check_date(&self, date: String) -> bool { chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").is_ok() @@ -522,6 +468,7 @@ impl egui_dock::TabViewer for WorkerTabViewer { match &tab.tab_type { TabTypes::WorkerList => self.show_worker(ui), TabTypes::WorkerPosition => self.show_position(ui), + TabTypes::WorkerPromotion => self.show_promotions(ui), _ => { ui.label("Nope"); } @@ -544,8 +491,14 @@ impl Default for WorkerTabViewer { let positions = Arc::new(RwLock::new( rt.block_on(async { db_oper.get_position().await.unwrap() }), )); - let workers = Arc::new(RwLock::new( - rt.block_on(async { db_oper.get_workers().await.unwrap() }), + let workers = Arc::new(RwLock::new(rt.block_on(async { + db_oper + .get_workers(0, misc::ELEMENTS_PER_PAGE, false) + .await + .unwrap() + }))); + let promotions = Arc::new(RwLock::new( + rt.block_on(async { db_oper.get_promotions(0, 5).await.unwrap() }), )); let mut pos_map: HashMap = HashMap::new(); pos_map.extend(|| -> Vec<(String, String)> { @@ -556,20 +509,7 @@ impl Default for WorkerTabViewer { ("wage".to_string(), pos.wage.to_string()), ] }()); - let mut worker_map: HashMap = HashMap::new(); - worker_map.extend(|| -> Vec<(String, String)> { - let worker = workers.read()[0].clone(); - vec![ - ("id".to_string(), worker.id.to_string()), - ("full_name".to_string(), worker.full_name), - ("position_id".to_string(), worker.position.id.to_string()), - ( - "hire_date".to_string(), - worker.hire_date.date_naive().format("%d-%m-%Y").to_string(), - ), - ("is_fired".to_string(), worker.is_fired.to_string()), - ] - }()); + let worker = workers.read()[0].clone(); Self { process_position_state: ModalWinState { is_open: false, @@ -577,18 +517,22 @@ impl Default for WorkerTabViewer { data: pos_map, status: ModalStatus::Add, }, - process_worker_state: ModalWinState { + process_worker_state: WorkerModalState { is_open: false, can_finish: false, - data: worker_map, + worker, status: ModalStatus::Add, + old_pos: Position::default(), }, workers, - positions: positions, + positions, + promotions, db_oper, rt, is_admin: false, + only_not_fired_workers: false, + workers_page: 0, } } }