я допишу я допишу я тесты напишу я всё напишу и защищусь

master
Алексей Алексей 2026-06-21 14:00:02 +10:00
parent 776f40cd11
commit 0c88a19a40
6 changed files with 180 additions and 20 deletions

View File

@ -174,7 +174,7 @@ impl eframe::App for App<'_> {
struct MainTabViewer { struct MainTabViewer {
workers: Arc<RwLock<Vec<Worker>>>, workers: Arc<RwLock<Vec<Worker>>>,
equipment: Arc<RwLock<Option<Vec<Equipment>>>>, equipment: Arc<RwLock<Vec<Equipment>>>,
salaries: Arc<RwLock<Vec<Salary>>>, salaries: Arc<RwLock<Vec<Salary>>>,
db_oper: DBOperator, db_oper: DBOperator,
@ -199,9 +199,9 @@ impl Default for MainTabViewer {
workers: Arc::new(RwLock::new( workers: Arc::new(RwLock::new(
rt.block_on(async { db_oper.get_workers().await.unwrap() }), rt.block_on(async { db_oper.get_workers().await.unwrap() }),
)), )),
equipment: Arc::new(RwLock::new(Option::Some( equipment: Arc::new(RwLock::new(
rt.block_on(async { db_oper.get_equipment().await.unwrap() }), rt.block_on(async { db_oper.get_equipment().await.unwrap() }),
))), )),
salaries: Arc::new(RwLock::new( salaries: Arc::new(RwLock::new(
rt.block_on(async { db_oper.get_salaries().await.unwrap() }), rt.block_on(async { db_oper.get_salaries().await.unwrap() }),
)), )),
@ -268,6 +268,12 @@ impl MainTabViewer {
.block_on(async { self.db_oper.get_salaries().await.unwrap() }), .block_on(async { self.db_oper.get_salaries().await.unwrap() }),
)); ));
} }
fn update_equipment(&mut self) {
self.equipment = Arc::new(RwLock::new(
self.rt
.block_on(async { self.db_oper.get_equipment().await.unwrap() }),
));
}
fn get_salary(&self) -> Salary { fn get_salary(&self) -> Salary {
Salary { Salary {
id: self id: self
@ -358,7 +364,7 @@ impl MainTabViewer {
}) })
.body(|mut body| { .body(|mut body| {
body.ui_mut().style_mut().interaction.selectable_labels = false; body.ui_mut().style_mut().interaction.selectable_labels = false;
for eq in self.equipment.read().clone().unwrap().iter() { for eq in self.equipment.read().clone().iter() {
body.row(20.0, |mut row| { body.row(20.0, |mut row| {
row.col(|ui| { row.col(|ui| {
ui.label(&eq.name); ui.label(&eq.name);
@ -452,7 +458,7 @@ impl MainTabViewer {
.unwrap() .unwrap()
.clone() .clone()
}) })
.unwrap() .unwrap_or(&Worker::default())
.full_name .full_name
.clone(); .clone();
egui::ComboBox::new("process_equipment_combobox", "") egui::ComboBox::new("process_equipment_combobox", "")
@ -610,6 +616,8 @@ impl MainTabViewer {
}); });
} }
} }
self.equipment_modal_state.is_open = false;
self.update_equipment();
} }
}); });
} }

View File

@ -6,6 +6,7 @@ use sqlx::mysql::MySqlPoolOptions;
use dotenvy::dotenv_override; use dotenvy::dotenv_override;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet;
use std::env; use std::env;
// use crate::schema::equipment::dsl::*; // use crate::schema::equipment::dsl::*;
// use crate::schema::worker::dsl::*; // use crate::schema::worker::dsl::*;
@ -448,11 +449,47 @@ impl DBOperator {
ret.push(Recipe { ret.push(Recipe {
id: pr.id, id: pr.id,
name: pr.name.clone(), name: pr.name.clone(),
elements: els, elements: els.into_iter().collect(),
}); });
} }
Ok(ret) Ok(ret)
} }
pub async fn add_recipe(&self, rec: Recipe) -> Result<u64, sqlx::Error> {
let mut tx = self.pool.begin().await?;
sqlx::query("INSERT INTO `recipe` (id,name) VALUES (?,?)")
.bind(rec.id)
.bind(rec.name)
.execute(&mut *tx)
.await?;
let ret = sqlx::query("SELECT LAST_INSERT_ID();")
.fetch_one(&mut *tx)
.await?;
match tx.commit().await {
Ok(_) => Ok(ret.get(0)),
Err(_) => Ok(0),
}
}
pub async fn remove_recipe(&self, rec_id: i32) -> Result<bool, sqlx::Error> {
match sqlx::query("DELETE FROM `recipe` WHERE id=?")
.bind(rec_id)
.execute(&self.pool)
.await
{
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}
pub async fn update_recipe(&self, recipe: Recipe) -> Result<bool, sqlx::Error> {
match sqlx::query("UPDATE Brewery.recipe SET name=? WHERE id=?;")
.bind(recipe.name)
.bind(recipe.id)
.execute(&self.pool)
.await
{
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}
pub async fn get_recipe_elements(&self) -> Result<Vec<RecipeElement>, sqlx::Error> { pub async fn get_recipe_elements(&self) -> Result<Vec<RecipeElement>, sqlx::Error> {
let pre_ret = sqlx::query_as::<_, RecipeElementRow>("SELECT id, recipe_id, material_id, equipment_id, quantity FROM Brewery.recipe_element;").fetch_all(&self.pool).await; let pre_ret = sqlx::query_as::<_, RecipeElementRow>("SELECT id, recipe_id, material_id, equipment_id, quantity FROM Brewery.recipe_element;").fetch_all(&self.pool).await;
@ -477,6 +514,36 @@ impl DBOperator {
} }
Ok(ret) Ok(ret)
} }
pub async fn add_recipe_elems(
&self,
elems: HashSet<RecipeElement>,
rec_id: i32,
) -> Result<bool, sqlx::Error> {
let mut tx = self.pool.begin().await?;
for el in elems.iter() {
sqlx::query("INSERT INTO `recipe_element` (id, recipe_id, material_id, equipment_id, quantity) VALUES(0, ?, ?, ?, ?);")
.bind(rec_id)
.bind(el.material.id)
.bind(el.equipment.id)
.bind(el.quantity)
.execute(&mut *tx)
.await?;
}
match tx.commit().await {
Ok(_) => Ok(true),
Err(e) => Err(e),
}
}
pub async fn remove_recipe_elements(&self, rec_id: i32) -> Result<bool, sqlx::Error> {
match sqlx::query("DELETE FROM `recipe_element` WHERE recipe_id=?")
.bind(rec_id)
.execute(&self.pool)
.await
{
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}
} }
pub async fn estabilish_connection() -> Result<(), sqlx::Error> { pub async fn estabilish_connection() -> Result<(), sqlx::Error> {

View File

@ -6,6 +6,7 @@ mod database;
mod material_tab; mod material_tab;
mod models; mod models;
mod payment_tab; mod payment_tab;
mod production_tab;
mod recipe_tab; mod recipe_tab;
mod reports; mod reports;
mod tab; mod tab;

View File

@ -111,7 +111,7 @@ pub struct RecipeElementRow {
pub struct Recipe { pub struct Recipe {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub elements: std::vec::Vec<RecipeElement>, pub elements: HashSet<RecipeElement>,
} }
#[derive(FromRow)] #[derive(FromRow)]
pub struct RecipeRow { pub struct RecipeRow {

View File

@ -0,0 +1,3 @@
pub struct ProductionTabViewer {
is_admin: bool,
}

View File

@ -1,3 +1,4 @@
use bigdecimal::ToPrimitive;
use egui_extras::{Column, TableBuilder}; use egui_extras::{Column, TableBuilder};
use crate::{ use crate::{
@ -37,6 +38,12 @@ impl RecipeTabViewer {
..Default::default() ..Default::default()
} }
} }
fn update_recipes(&mut self) {
self.recipes =
Arc::new(RwLock::new(self.rt.block_on(async {
self.db_oper.get_recipes().await.unwrap_or(Vec::new())
})));
}
fn new_element_modal(&mut self, ui: &mut egui::Ui) { fn new_element_modal(&mut self, ui: &mut egui::Ui) {
egui::Modal::new(egui::Id::new("new_element_modal")).show(ui.ctx(), |ui| { egui::Modal::new(egui::Id::new("new_element_modal")).show(ui.ctx(), |ui| {
if ui.button("Закрыть").clicked() { if ui.button("Закрыть").clicked() {
@ -78,8 +85,12 @@ impl RecipeTabViewer {
} }
}); });
}); });
if ui.button("Добавить").clicked(){ if ui.button("Добавить").clicked() {
self.process_recipe_status
.recipe
.elements
.insert(self.process_element_status.element.clone());
self.process_element_status.is_open = false;
} }
}); });
} }
@ -100,29 +111,99 @@ impl RecipeTabViewer {
ui.end_row(); ui.end_row();
ui.label("Составляющие:"); ui.label("Составляющие:");
let text = match self.process_recipe_status.recipe.elements.len() {
0 => "Пусто",
1 => "1 элемент",
2..=4 => &format!(
"{} элемента",
self.process_recipe_status.recipe.elements.len()
),
_ => &format!(
"{} элементов",
self.process_recipe_status.recipe.elements.len()
),
};
egui::ComboBox::new("recipe_element_combobox", "") egui::ComboBox::new("recipe_element_combobox", "")
.close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
.selected_text(text)
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
if ui.button("Новый элемент").clicked() { if ui.button("Новый элемент").clicked() {
self.process_element_status.is_open = true; self.process_element_status.is_open = true;
self.process_element_status.status = ModalStatus::Add; self.process_element_status.status = ModalStatus::Add;
self.process_element_status.element = RecipeElement::default();
} }
for el in self.recipe_elements.read().clone().iter() { for el in self.process_recipe_status.recipe.elements.clone().iter() {
let resp = ui.checkbox( ui.horizontal(|ui| {
&mut self.process_recipe_status.elements.contains(el), if ui.button("x").clicked() {
&el.material.name, self.process_recipe_status.recipe.elements.remove(el);
);
if resp.clicked() {
if self.process_recipe_status.elements.contains(el) {
self.process_recipe_status.elements.remove(el);
} else {
self.process_recipe_status.elements.insert(el.clone());
}
} }
ui.label(format!(
"{} ({})\n{}",
el.material.name, el.material.category.name, el.equipment.name
));
});
} }
}); });
}); });
let btext = match self.process_recipe_status.status {
ModalStatus::Add => "Добавить",
ModalStatus::Edit => "Сохранить",
ModalStatus::Remove => "Удалить",
};
if ui.button(btext).clicked() {
match self.process_recipe_status.status {
ModalStatus::Add => {
self.rt.block_on(async {
let a = self
.db_oper
.add_recipe(self.process_recipe_status.recipe.clone())
.await
.ok();
self.db_oper
.add_recipe_elems(
self.process_recipe_status.recipe.elements.clone(),
a.unwrap().to_i32().unwrap(),
)
.await
.ok();
});
}
ModalStatus::Edit => {
self.rt.block_on(async {
self.db_oper
.update_recipe(self.process_recipe_status.recipe.clone())
.await
.ok();
self.db_oper
.remove_recipe_elements(self.process_recipe_status.recipe.id)
.await
.ok();
self.db_oper
.add_recipe_elems(
self.process_recipe_status.recipe.elements.clone(),
self.process_recipe_status.recipe.id,
)
.await
.ok();
});
}
ModalStatus::Remove => {
self.rt.block_on(async {
self.db_oper
.remove_recipe_elements(self.process_recipe_status.recipe.id)
.await
.ok();
self.db_oper
.remove_recipe(self.process_recipe_status.recipe.id)
.await
.ok();
});
}
}
self.update_recipes();
self.process_recipe_status.is_open = false;
}
}); });
if self.process_element_status.is_open { if self.process_element_status.is_open {
self.new_element_modal(ui); self.new_element_modal(ui);