сегодня защита и оно почти готово

master
Алексей Алексей 2026-06-26 08:35:35 +10:00
parent 09ed906aa7
commit 69525d37d2
6 changed files with 401 additions and 12 deletions

View File

@ -6,6 +6,7 @@ use std::{
}; };
use crate::{ use crate::{
client_tab::ClientTab,
database::DBOperator, database::DBOperator,
material_tab::MaterialTabViewer, material_tab::MaterialTabViewer,
misc::{self, ELEMENTS_PER_PAGE}, misc::{self, ELEMENTS_PER_PAGE},
@ -195,6 +196,7 @@ struct MainTabViewer {
recipe_tab: RecipeTabViewer, recipe_tab: RecipeTabViewer,
order_tabs: OrderTabViewer, order_tabs: OrderTabViewer,
order_tree: egui_dock::DockState<Tab>, order_tree: egui_dock::DockState<Tab>,
client_tab: ClientTab,
equipment_modal_state: EquipmentModalState, equipment_modal_state: EquipmentModalState,
salary_modal_state: SalaryModalState, salary_modal_state: SalaryModalState,
@ -265,7 +267,7 @@ impl Default for MainTabViewer {
tab_type: TabTypes::Recipe, tab_type: TabTypes::Recipe,
}, },
Tab { Tab {
title: "Производство".to_owned(), title: "Продукция".to_owned(),
tab_type: TabTypes::Product, tab_type: TabTypes::Product,
}, },
]), ]),
@ -280,6 +282,7 @@ impl Default for MainTabViewer {
tab_type: TabTypes::OutcomingOrderList, tab_type: TabTypes::OutcomingOrderList,
}, },
]), ]),
client_tab: ClientTab::default(),
rt, rt,
is_dark_theme: true, is_dark_theme: true,
interface_scale_ratio: 1.2, interface_scale_ratio: 1.2,
@ -389,6 +392,7 @@ impl MainTabViewer {
material_tabs: MaterialTabViewer::new(is_admin), material_tabs: MaterialTabViewer::new(is_admin),
recipe_tab: RecipeTabViewer::new(is_admin), recipe_tab: RecipeTabViewer::new(is_admin),
order_tabs: OrderTabViewer::new(is_admin), order_tabs: OrderTabViewer::new(is_admin),
client_tab: ClientTab::new(is_admin),
..Default::default() ..Default::default()
} }
} }
@ -1024,6 +1028,9 @@ impl egui_dock::TabViewer for MainTabViewer {
TabTypes::Order => { TabTypes::Order => {
&self.show_order(ui); &self.show_order(ui);
} }
TabTypes::Client => {
&self.client_tab.show_tab(ui);
}
_ => { _ => {
ui.label("Welcome to Avalonia!"); ui.label("Welcome to Avalonia!");
} }

204
code/src/client_tab.rs Normal file
View File

@ -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<RwLock<Vec<Client>>>,
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(),
}
}
}

View File

@ -789,6 +789,68 @@ impl DBOperator {
None => Err(sqlx::Error::BeginFailed), None => Err(sqlx::Error::BeginFailed),
} }
} }
pub async fn get_order_id_by_order_element_id(
&self,
element_id: i32,
) -> Result<i32, sqlx::Error> {
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<Client> {
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> { pub async fn estabilish_connection() -> Result<(), sqlx::Error> {

View File

@ -1,15 +1,16 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod app; mod app;
mod client_tab;
mod database; mod database;
mod material_tab; mod material_tab;
mod misc;
mod models; mod models;
mod order_tab; mod order_tab;
mod recipe_tab; mod recipe_tab;
mod reports; mod reports;
mod tab; mod tab;
mod worker_tab; mod worker_tab;
mod misc;
fn main() { fn main() {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();

View File

@ -125,7 +125,7 @@ pub struct RecipeRow {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
} }
#[derive(FromRow, Default, PartialEq, Eq, PartialOrd, Ord)] #[derive(FromRow, Default, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Client { pub struct Client {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
@ -270,6 +270,13 @@ pub struct OrderModalState {
pub order: Order, pub order: Order,
pub status: ModalStatus, 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)] #[derive(Default, PartialEq)]
pub enum ModalStatus { pub enum ModalStatus {

View File

@ -7,7 +7,9 @@ use egui_extras::Column;
use crate::{ use crate::{
database::DBOperator, database::DBOperator,
misc, misc,
models::{ModalStatus, Order, OrderElement, OrderElementModalState, OrderModalState}, models::{
Client, ModalStatus, Order, OrderElement, OrderElementModalState, OrderModalState, Product,
},
tab::{TABS_CAN_BE_WINDOWS, Tab, TabTypes}, tab::{TABS_CAN_BE_WINDOWS, Tab, TabTypes},
}; };
@ -17,12 +19,16 @@ pub struct OrderTabViewer {
incoming_orders: Arc<RwLock<Vec<Order>>>, incoming_orders: Arc<RwLock<Vec<Order>>>,
outcoming_orders: Arc<RwLock<Vec<Order>>>, outcoming_orders: Arc<RwLock<Vec<Order>>>,
products: Arc<RwLock<Vec<Product>>>,
clients: Arc<RwLock<Vec<Client>>>,
incoming_order_page: i32, incoming_order_page: i32,
outcoming_order_page: i32, outcoming_order_page: i32,
products_page: i32,
is_admin: bool, is_admin: bool,
process_order_state: OrderModalState, process_order_state: OrderModalState,
process_element_state: OrderElementModalState, process_element_state: OrderElementModalState,
client_page: i32,
} }
impl OrderTabViewer { 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) { fn show_order_element_modal(&mut self, ui: &mut egui::Ui) {
egui::Modal::new(egui::Id::new("order_element_modal")).show(ui.ctx(), |ui| { egui::Modal::new(egui::Id::new("order_element_modal")).show(ui.ctx(), |ui| {
if ui.button("Закрыть").clicked() { if ui.button("Закрыть").clicked() {
@ -61,7 +77,44 @@ impl OrderTabViewer {
} }
egui::Grid::new("order_element_grid").show(ui, |ui| { egui::Grid::new("order_element_grid").show(ui, |ui| {
ui.label("Продукт:"); 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.end_row();
ui.label("Количество:"); ui.label("Количество:");
@ -80,7 +133,39 @@ impl OrderTabViewer {
.price_per_unit .price_per_unit
.clone() .clone()
* self.process_element_state.element.quantity * 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 ui.button("<-").clicked() {
if incoming_orders { if incoming_orders {
if self.incoming_orders.read().len() > 0 { if self.incoming_order_page > 0 {
self.incoming_order_page -= 1; self.incoming_order_page -= 1;
self.update_orders(); self.update_orders();
} }
} else { } else {
if self.outcoming_orders.read().len() > 0 { if self.outcoming_order_page > 0 {
self.outcoming_order_page -= 1; self.outcoming_order_page -= 1;
self.update_orders(); self.update_orders();
} }
@ -185,16 +270,20 @@ impl OrderTabViewer {
self.process_order_state.is_open = false self.process_order_state.is_open = false
} }
egui::Grid::new("order_process_grid").show(ui, |ui| { egui::Grid::new("order_process_grid").show(ui, |ui| {
if self.process_order_state.status != ModalStatus::Add {
ui.label("ID заказа:"); ui.label("ID заказа:");
ui.label(self.process_order_state.order.id.to_string()); ui.label(self.process_order_state.order.id.to_string());
ui.end_row(); ui.end_row();
}
ui.label(if incoming_orders { ui.label(if incoming_orders {
"Заказчик" "Заказчик"
} else { } else {
"Клиент" "Клиент"
}); });
//ВЫБОР КЛИЕНТА egui::ComboBox::new("select_client_combobox", "")
.selected_text(&self.process_order_state.order.client.name)
.show_ui(ui, |ui| {});
ui.end_row(); ui.end_row();
ui.label("Элементы"); ui.label("Элементы");
@ -232,6 +321,17 @@ impl OrderTabViewer {
self.show_order_element_modal(ui); 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 { impl egui_dock::TabViewer for OrderTabViewer {
@ -273,9 +373,17 @@ impl Default for OrderTabViewer {
outcoming_orders: Arc::new(RwLock::new( outcoming_orders: Arc::new(RwLock::new(
rt.block_on(async { db_oper.get_orders(0, misc::ELEMENTS_PER_PAGE, false).await }), 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, incoming_order_page: 0,
outcoming_order_page: 0, outcoming_order_page: 0,
products_page: 0,
client_page: 0,
db_oper, db_oper,
rt, rt,
is_admin: false, is_admin: false,