From 69525d37d266c14e5c83f15c433f27bfbb222c90 Mon Sep 17 00:00:00 2001 From: ultrageese Date: Fri, 26 Jun 2026 08:35:35 +1000 Subject: [PATCH] =?UTF-8?q?=D1=81=D0=B5=D0=B3=D0=BE=D0=B4=D0=BD=D1=8F=20?= =?UTF-8?q?=D0=B7=D0=B0=D1=89=D0=B8=D1=82=D0=B0=20=D0=B8=20=D0=BE=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=BE=D1=87=D1=82=D0=B8=20=D0=B3=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/src/app.rs | 9 +- code/src/client_tab.rs | 204 +++++++++++++++++++++++++++++++++++++++++ code/src/database.rs | 62 +++++++++++++ code/src/main.rs | 3 +- code/src/models.rs | 9 +- code/src/order_tab.rs | 126 +++++++++++++++++++++++-- 6 files changed, 401 insertions(+), 12 deletions(-) create mode 100644 code/src/client_tab.rs diff --git a/code/src/app.rs b/code/src/app.rs index 1c66bed..c39ce9e 100644 --- a/code/src/app.rs +++ b/code/src/app.rs @@ -6,6 +6,7 @@ use std::{ }; use crate::{ + client_tab::ClientTab, database::DBOperator, material_tab::MaterialTabViewer, misc::{self, ELEMENTS_PER_PAGE}, @@ -195,6 +196,7 @@ struct MainTabViewer { recipe_tab: RecipeTabViewer, order_tabs: OrderTabViewer, order_tree: egui_dock::DockState, + client_tab: ClientTab, equipment_modal_state: EquipmentModalState, salary_modal_state: SalaryModalState, @@ -265,7 +267,7 @@ impl Default for MainTabViewer { tab_type: TabTypes::Recipe, }, Tab { - title: "Производство".to_owned(), + title: "Продукция".to_owned(), tab_type: TabTypes::Product, }, ]), @@ -280,6 +282,7 @@ impl Default for MainTabViewer { tab_type: TabTypes::OutcomingOrderList, }, ]), + client_tab: ClientTab::default(), rt, is_dark_theme: true, interface_scale_ratio: 1.2, @@ -389,6 +392,7 @@ impl MainTabViewer { material_tabs: MaterialTabViewer::new(is_admin), recipe_tab: RecipeTabViewer::new(is_admin), order_tabs: OrderTabViewer::new(is_admin), + client_tab: ClientTab::new(is_admin), ..Default::default() } } @@ -1024,6 +1028,9 @@ impl egui_dock::TabViewer for MainTabViewer { TabTypes::Order => { &self.show_order(ui); } + TabTypes::Client => { + &self.client_tab.show_tab(ui); + } _ => { ui.label("Welcome to Avalonia!"); } diff --git a/code/src/client_tab.rs b/code/src/client_tab.rs new file mode 100644 index 0000000..8640dc7 --- /dev/null +++ b/code/src/client_tab.rs @@ -0,0 +1,204 @@ +use std::sync::Arc; + +use bigdecimal::ToPrimitive; +use egui::{Sense, mutex::RwLock}; +use egui_extras::Column; + +use crate::{ + database::DBOperator, + misc, + models::{Client, ClientModalState, ModalStatus}, +}; + +pub struct ClientTab { + db_oper: DBOperator, + rt: tokio::runtime::Runtime, + + clients: Arc>>, + + client_page: i32, + is_admin: bool, + + process_client_state: ClientModalState, +} +impl ClientTab { + fn update_clients(&mut self) { + self.clients = Arc::new(RwLock::new(self.rt.block_on(async { + self.db_oper + .get_clients( + self.client_page * misc::ELEMENTS_PER_PAGE, + misc::ELEMENTS_PER_PAGE, + ) + .await + }))); + } + pub fn new(is_admin: bool) -> Self { + Self { + is_admin, + ..Default::default() + } + } + pub fn show_tab(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + if self.is_admin { + if ui.button("Добавить").clicked() { + self.process_client_state.is_open = true; + self.process_client_state.status = ModalStatus::Add; + self.process_client_state.client = Client::default(); + } + } + if ui.button("<-").clicked() { + if self.client_page > 0 { + self.client_page -= 1; + self.update_clients(); + } + } + ui.monospace(self.client_page.to_string()); + if ui.button("->").clicked() { + if self.clients.read().len().to_i32().unwrap() == misc::ELEMENTS_PER_PAGE { + self.client_page += 1; + self.update_clients(); + } + } + }); + egui_extras::TableBuilder::new(ui) + .striped(true) + .vscroll(false) + .sense(Sense::click()) + .columns(Column::auto(), 3) + .header(25.0, |mut head| { + head.col(|ui| { + ui.heading("Название "); + }); + head.col(|ui| { + ui.heading("Адрес"); + }); + head.col(|ui| { + ui.heading(" "); + }); + }) + .body(|mut body| { + for cl in self.clients.read().iter() { + body.row(25.0, |mut row| { + row.col(|ui| { + ui.label(cl.name.clone()); + }); + row.col(|ui| { + ui.label(cl.address.clone()); + }); + if self.is_admin { + row.col(|ui| { + if ui.button("Удалить").clicked() { + self.process_client_state.is_open = true; + self.process_client_state.client = cl.clone(); + self.process_client_state.status = ModalStatus::Remove; + } + }); + if row.response().double_clicked() { + self.process_client_state.is_open = true; + self.process_client_state.client = cl.clone(); + self.process_client_state.status = ModalStatus::Edit; + } + } + }); + } + }); + if self.process_client_state.is_open { + egui::Modal::new(egui::Id::new("process_client_modal")).show(ui.ctx(), |ui| { + if ui.button("Закрыть").clicked() { + self.process_client_state.is_open = false; + } + if self.process_client_state.status != ModalStatus::Remove { + egui::Grid::new("process_client_grid").show(ui, |ui| { + let mut size = ui.available_size(); + size.x = 80.0; + ui.label("Имя клиента:"); + ui.add_sized( + size, + egui::TextEdit::singleline(&mut self.process_client_state.client.name), + ); + ui.end_row(); + + ui.label("Адрес клиента:"); + ui.add_sized( + size, + egui::TextEdit::singleline( + &mut self.process_client_state.client.address, + ), + ); + ui.end_row(); + }); + } else { + ui.label(format!( + "Удалить {}?", + self.process_client_state.client.name + )); + } + let btext = match self.process_client_state.status { + ModalStatus::Add => "Добавить", + ModalStatus::Edit => "Сохранить", + ModalStatus::Remove => "Удалить", + }; + self.process_client_state.can_finish = match self.process_client_state.status { + ModalStatus::Remove => true, + _ => { + self.process_client_state.client.name.len() > 0 + && self.process_client_state.client.address.len() > 0 + } + }; + if ui + .add_enabled( + self.process_client_state.can_finish, + egui::Button::new(btext), + ) + .clicked() + { + match self.process_client_state.status { + ModalStatus::Add => { + self.rt.block_on(async { + self.db_oper + .add_client(self.process_client_state.client.clone()) + .await + .ok() + }); + } + ModalStatus::Edit => { + self.rt.block_on(async { + self.db_oper + .update_client(self.process_client_state.client.clone()) + .await + .ok() + }); + } + ModalStatus::Remove => { + self.rt.block_on(async { + self.db_oper + .remove_client(self.process_client_state.client.id) + .await + .ok() + }); + } + } + self.update_clients(); + self.process_client_state.is_open = false; + } + }); + } + } +} +impl Default for ClientTab { + fn default() -> Self { + let rt = tokio::runtime::Runtime::new().unwrap(); + let db_oper = rt.block_on(async { DBOperator::new().await }); + Self { + clients: Arc::new(RwLock::new(rt.block_on(async { + db_oper.get_clients(0, misc::ELEMENTS_PER_PAGE).await + }))), + db_oper, + rt, + is_admin: Default::default(), + client_page: 0, + process_client_state: ClientModalState::default(), + } + } +} diff --git a/code/src/database.rs b/code/src/database.rs index 128bb82..0a647ea 100644 --- a/code/src/database.rs +++ b/code/src/database.rs @@ -789,6 +789,68 @@ impl DBOperator { None => Err(sqlx::Error::BeginFailed), } } + pub async fn get_order_id_by_order_element_id( + &self, + element_id: i32, + ) -> Result { + let ret: i32 = sqlx::query_scalar("SELECT order_id FROM order_element WHERE id=?") + .bind(element_id) + .fetch_one(&self.pool) + .await?; + Ok(ret) + } + pub async fn add_order_element(&self, order_elem: OrderElement) -> Result<(), sqlx::Error> { + match sqlx::query("INSERT INTO order_element (order_id, product_id, quantity, total_amount) VALUES(?, ?, ?, ?);") + .bind(self.get_order_id_by_order_element_id(order_elem.id).await.unwrap()) + .bind(order_elem.product.id) + .bind(order_elem.quantity) + .bind(order_elem.total_price) + .execute(&self.pool).await{ + Ok(_) => Ok(()), + Err(e) => Err(e) + } + } + pub async fn get_clients(&self, start: i32, length: i32) -> Vec { + let pret = sqlx::query_as::<_, Client>("SELECT * FROM client LIMIT ? OFFSET ?") + .bind(length) + .bind(start) + .fetch_all(&self.pool) + .await; + pret.unwrap() + } + pub async fn add_client(&self, client: Client) -> Result<(), sqlx::Error> { + match sqlx::query("INSERT INTO client (name, address) VALUES(?, ?);") + .bind(client.name) + .bind(client.address) + .execute(&self.pool) + .await + { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + pub async fn update_client(&self, client: Client) -> Result<(), sqlx::Error> { + match sqlx::query("UPDATE client SET name=?, address=? WHERE id=?;") + .bind(client.name) + .bind(client.address) + .bind(client.id) + .execute(&self.pool) + .await + { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + pub async fn remove_client(&self, client_id: i32) -> Result<(), sqlx::Error> { + match sqlx::query("DELETE FROM client WHERE id=?") + .bind(client_id) + .execute(&self.pool) + .await + { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } } pub async fn estabilish_connection() -> Result<(), sqlx::Error> { diff --git a/code/src/main.rs b/code/src/main.rs index 45d0fa6..3973d09 100644 --- a/code/src/main.rs +++ b/code/src/main.rs @@ -1,15 +1,16 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod app; +mod client_tab; mod database; mod material_tab; +mod misc; mod models; mod order_tab; mod recipe_tab; mod reports; mod tab; mod worker_tab; -mod misc; fn main() { dotenvy::dotenv().ok(); diff --git a/code/src/models.rs b/code/src/models.rs index 168bca4..efb2523 100644 --- a/code/src/models.rs +++ b/code/src/models.rs @@ -125,7 +125,7 @@ pub struct RecipeRow { pub id: i32, pub name: String, } -#[derive(FromRow, Default, PartialEq, Eq, PartialOrd, Ord)] +#[derive(FromRow, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct Client { pub id: i32, pub name: String, @@ -270,6 +270,13 @@ pub struct OrderModalState { pub order: Order, pub status: ModalStatus, } +#[derive(Default)] +pub struct ClientModalState { + pub is_open: bool, + pub can_finish: bool, + pub client: Client, + pub status: ModalStatus, +} #[derive(Default, PartialEq)] pub enum ModalStatus { diff --git a/code/src/order_tab.rs b/code/src/order_tab.rs index 6f9976c..c656298 100644 --- a/code/src/order_tab.rs +++ b/code/src/order_tab.rs @@ -7,7 +7,9 @@ use egui_extras::Column; use crate::{ database::DBOperator, misc, - models::{ModalStatus, Order, OrderElement, OrderElementModalState, OrderModalState}, + models::{ + Client, ModalStatus, Order, OrderElement, OrderElementModalState, OrderModalState, Product, + }, tab::{TABS_CAN_BE_WINDOWS, Tab, TabTypes}, }; @@ -17,12 +19,16 @@ pub struct OrderTabViewer { incoming_orders: Arc>>, outcoming_orders: Arc>>, + products: Arc>>, + clients: Arc>>, incoming_order_page: i32, outcoming_order_page: i32, + products_page: i32, is_admin: bool, process_order_state: OrderModalState, process_element_state: OrderElementModalState, + client_page: i32, } impl OrderTabViewer { @@ -54,6 +60,16 @@ impl OrderTabViewer { )); }); } + fn update_clients(&mut self) { + self.clients = Arc::new(RwLock::new(self.rt.block_on(async { + self.db_oper + .get_clients( + self.client_page * misc::ELEMENTS_PER_PAGE, + misc::ELEMENTS_PER_PAGE, + ) + .await + }))); + } fn show_order_element_modal(&mut self, ui: &mut egui::Ui) { egui::Modal::new(egui::Id::new("order_element_modal")).show(ui.ctx(), |ui| { if ui.button("Закрыть").clicked() { @@ -61,7 +77,44 @@ impl OrderTabViewer { } egui::Grid::new("order_element_grid").show(ui, |ui| { ui.label("Продукт:"); - /// + egui::ComboBox::new("process_element_combobox", "") + .selected_text( + self.process_element_state + .element + .product + .recipe + .name + .clone(), + ) + .show_ui(ui, |ui| { + ui.horizontal(|ui| { + if ui.button("<-").clicked() { + if self.products_page > 0 { + self.products_page -= 1; + self.update_products(); + } + } + ui.monospace(self.products_page.to_string()); + if ui.button("->").clicked() { + if self.products.read().len().to_i32().unwrap() + == misc::ELEMENTS_PER_PAGE + { + self.products_page += 1; + self.update_products(); + } + } + }); + for prod in self.products.read().clone() { + ui.selectable_value( + &mut self.process_element_state.element.product, + prod.clone(), + format!( + "{} {} ед. | {}/шт.", + prod.recipe.name, prod.volume, prod.price_per_unit + ), + ); + } + }); ui.end_row(); ui.label("Количество:"); @@ -80,7 +133,39 @@ impl OrderTabViewer { .price_per_unit .clone() * self.process_element_state.element.quantity - )) + )); + ui.end_row(); + let btext = match self.process_element_state.status { + ModalStatus::Add => "Добавить", + _ => "Подтвердить", + }; + self.process_element_state.can_finish = match self.process_element_state.status { + ModalStatus::Add => { + self.process_element_state.element.product.id != 0 + && self.process_element_state.element.quantity > 0 + } + _ => true, + }; + if ui + .add_enabled( + self.process_element_state.can_finish, + egui::Button::new(btext), + ) + .clicked() + { + self.process_element_state.element.total_price = self + .process_element_state + .element + .product + .price_per_unit + .clone() + * self.process_element_state.element.quantity; + self.process_order_state + .order + .elements + .insert(self.process_element_state.element.clone()); + self.process_element_state.is_open = false; + } }); }); } @@ -100,12 +185,12 @@ impl OrderTabViewer { if ui.button("<-").clicked() { if incoming_orders { - if self.incoming_orders.read().len() > 0 { + if self.incoming_order_page > 0 { self.incoming_order_page -= 1; self.update_orders(); } } else { - if self.outcoming_orders.read().len() > 0 { + if self.outcoming_order_page > 0 { self.outcoming_order_page -= 1; self.update_orders(); } @@ -185,16 +270,20 @@ impl OrderTabViewer { self.process_order_state.is_open = false } egui::Grid::new("order_process_grid").show(ui, |ui| { - ui.label("ID заказа:"); - ui.label(self.process_order_state.order.id.to_string()); - ui.end_row(); + if self.process_order_state.status != ModalStatus::Add { + ui.label("ID заказа:"); + ui.label(self.process_order_state.order.id.to_string()); + ui.end_row(); + } ui.label(if incoming_orders { "Заказчик" } else { "Клиент" }); - //ВЫБОР КЛИЕНТА + egui::ComboBox::new("select_client_combobox", "") + .selected_text(&self.process_order_state.order.client.name) + .show_ui(ui, |ui| {}); ui.end_row(); ui.label("Элементы"); @@ -232,6 +321,17 @@ impl OrderTabViewer { self.show_order_element_modal(ui); } } + + fn update_products(&mut self) { + self.products = Arc::new(RwLock::new(self.rt.block_on(async { + self.db_oper + .get_products( + self.products_page * misc::ELEMENTS_PER_PAGE, + misc::ELEMENTS_PER_PAGE, + ) + .await + }))); + } } impl egui_dock::TabViewer for OrderTabViewer { @@ -273,9 +373,17 @@ impl Default for OrderTabViewer { outcoming_orders: Arc::new(RwLock::new( rt.block_on(async { db_oper.get_orders(0, misc::ELEMENTS_PER_PAGE, false).await }), )), + products: Arc::new(RwLock::new(rt.block_on(async { + db_oper.get_products(0, misc::ELEMENTS_PER_PAGE).await + }))), + clients: Arc::new(RwLock::new(rt.block_on(async { + db_oper.get_clients(0, misc::ELEMENTS_PER_PAGE).await + }))), incoming_order_page: 0, outcoming_order_page: 0, + products_page: 0, + client_page: 0, db_oper, rt, is_admin: false,