Compare commits

..

2 Commits

10 changed files with 630 additions and 296 deletions

View File

@ -8,12 +8,13 @@ use std::{
use crate::{ use crate::{
database::DBOperator, database::DBOperator,
material_tab::MaterialTabViewer, material_tab::MaterialTabViewer,
misc, misc::{self, ELEMENTS_PER_PAGE},
models::{ models::{
Equipment, EquipmentFilter, EquipmentModalState, LoginModalState, LoginStatus, Material, Equipment, EquipmentFilter, EquipmentModalState, LoginModalState, LoginStatus, Material,
MaterialCategory, MaterialRow, ModalStatus, ModalWinState, Position, Salary, MaterialCategory, MaterialRow, ModalStatus, ModalWinState, Position, Salary,
SalaryModalState, Worker, SalaryModalState, Worker,
}, },
order_tab::OrderTabViewer,
recipe_tab::RecipeTabViewer, recipe_tab::RecipeTabViewer,
reports::Reporter, reports::Reporter,
tab::*, tab::*,
@ -22,7 +23,7 @@ use crate::{
use bigdecimal::ToPrimitive; use bigdecimal::ToPrimitive;
use chrono::{Datelike, NaiveDate}; use chrono::{Datelike, NaiveDate};
use egui::{TextBuffer, Vec2, mutex::RwLock}; 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 egui_extras::{Column, TableBuilder};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use sqlx::types::BigDecimal; use sqlx::types::BigDecimal;
@ -192,17 +193,21 @@ struct MainTabViewer {
material_tabs: MaterialTabViewer, material_tabs: MaterialTabViewer,
material_tree: egui_dock::DockState<Tab>, material_tree: egui_dock::DockState<Tab>,
recipe_tab: RecipeTabViewer, recipe_tab: RecipeTabViewer,
order_tabs: OrderTabViewer,
order_tree: egui_dock::DockState<Tab>,
equipment_modal_state: EquipmentModalState, equipment_modal_state: EquipmentModalState,
salary_modal_state: SalaryModalState, salary_modal_state: SalaryModalState,
is_dark_theme: bool, is_dark_theme: bool,
interface_scale_ratio: f32, interface_scale_ratio: f32,
page: i32, equipment_page: i32,
elements_per_page: i32, salaries_page: i32,
workers_page: i32,
is_admin: bool, is_admin: bool,
reporter: Reporter, reporter: Reporter,
only_hired_workers: bool,
} }
impl Default for MainTabViewer { impl Default for MainTabViewer {
fn default() -> Self { fn default() -> Self {
@ -210,15 +215,21 @@ impl Default for MainTabViewer {
let db_oper = rt.block_on(async { DBOperator::new().await }); let db_oper = rt.block_on(async { DBOperator::new().await });
let elements_per_page = 5; let elements_per_page = 5;
Self { Self {
workers: Arc::new(RwLock::new( workers: Arc::new(RwLock::new(rt.block_on(async {
rt.block_on(async { db_oper.get_workers().await.unwrap() }), db_oper
)), .get_workers(0, elements_per_page, false)
equipment: Arc::new(RwLock::new(rt.block_on(async { .await
db_oper.get_equipment(0, elements_per_page).await.unwrap() .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, db_oper,
worker_tabs: WorkerTabViewer::default(), worker_tabs: WorkerTabViewer::default(),
worker_tree: egui_dock::DockState::new(vec![ worker_tree: egui_dock::DockState::new(vec![
@ -247,6 +258,18 @@ impl Default for MainTabViewer {
}, },
]), ]),
recipe_tab: RecipeTabViewer::default(), 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, rt,
is_dark_theme: true, is_dark_theme: true,
interface_scale_ratio: 1.2, interface_scale_ratio: 1.2,
@ -273,22 +296,45 @@ impl Default for MainTabViewer {
is_admin: false, is_admin: false,
reporter: Reporter {}, reporter: Reporter {},
elements_per_page, equipment_page: 0,
page: 0, salaries_page: 0,
workers_page: 0,
only_hired_workers: false,
} }
} }
} }
impl MainTabViewer { impl MainTabViewer {
fn update_salary(&mut self) { fn update_salary(&mut self) {
self.salaries = Arc::new(RwLock::new( self.salaries = Arc::new(RwLock::new(self.rt.block_on(async {
self.rt self.db_oper
.block_on(async { self.db_oper.get_salaries().await.unwrap() }), .get_salaries(
)); self.salaries_page * misc::ELEMENTS_PER_PAGE,
misc::ELEMENTS_PER_PAGE,
)
.await
.unwrap()
})));
} }
fn update_equipment(&mut self) { fn update_equipment(&mut self) {
self.equipment = Arc::new(RwLock::new(self.rt.block_on(async { self.equipment = Arc::new(RwLock::new(self.rt.block_on(async {
self.db_oper 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 .await
.unwrap() .unwrap()
}))); })));
@ -345,10 +391,31 @@ impl MainTabViewer {
} }
}); });
} }
ui.add(egui::Checkbox::new( ui.horizontal(|ui| {
&mut self.equipment_modal_state.filters.without_written_offs, 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) TableBuilder::new(ui)
.striped(true) .striped(true)
.resizable(false) .resizable(false)
@ -381,47 +448,42 @@ 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().iter() { for eq in self.equipment.read().clone().iter() {
if (self.equipment_modal_state.filters.without_written_offs body.row(20.0, |mut row| {
&& !eq.is_written_off) row.col(|ui| {
|| !self.equipment_modal_state.filters.without_written_offs ui.label(&eq.name);
{ });
body.row(20.0, |mut row| { row.col(|ui| {
row.col(|ui| { ui.label(&eq.inv_number);
ui.label(&eq.name); });
}); row.col(|ui| {
row.col(|ui| { ui.label(&eq.worker.full_name);
ui.label(&eq.inv_number); });
}); row.col(|ui| {
row.col(|ui| { ui.label(eq.maintenance_date.date_naive().to_string());
ui.label(&eq.worker.full_name); });
}); row.col(|ui| {
row.col(|ui| { ui.label(if eq.is_written_off { "Да" } else { "Нет" });
ui.label(eq.maintenance_date.date_naive().to_string()); });
}); row.col(|ui| {
row.col(|ui| { if self.is_admin {
ui.label(if eq.is_written_off { "Да" } else { "Нет" }); if ui.button("Списать").clicked() {
}); self.equipment_modal_state.is_open = true;
row.col(|ui| { self.equipment_modal_state.status = ModalStatus::Remove;
if self.is_admin { self.equipment_modal_state.equipment = eq.clone();
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 { if self.equipment_modal_state.is_open {
egui::Modal::new(egui::Id::new("process_equipment_modal")).show(ui.ctx(), |ui| { 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)) if ui.button("Закрыть").clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape))
{ {
self.equipment_modal_state.is_open = false; self.equipment_modal_state.is_open = false;
@ -449,6 +511,8 @@ impl MainTabViewer {
ui.label("Ответственный"); ui.label("Ответственный");
egui::ComboBox::new("process_equipment_combobox", "") egui::ComboBox::new("process_equipment_combobox", "")
.close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
.height(100.0)
.selected_text( .selected_text(
self.equipment_modal_state self.equipment_modal_state
.equipment .equipment
@ -464,6 +528,23 @@ impl MainTabViewer {
&wkrk.full_name, &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(); ui.end_row();
@ -561,6 +642,12 @@ impl MainTabViewer {
.id(id) .id(id)
.show_inside(ui, &mut self.material_tabs); .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) { fn show_salary(&mut self, ui: &mut egui::Ui) {
/* /*
* Чё надо: * Чё надо:
@ -920,6 +1007,9 @@ impl egui_dock::TabViewer for MainTabViewer {
TabTypes::Recipe => { TabTypes::Recipe => {
&self.show_recipe(ui); &self.show_recipe(ui);
} }
TabTypes::Order => {
&self.show_order(ui);
}
_ => { _ => {
ui.label("Welcome to Avalonia!"); ui.label("Welcome to Avalonia!");
} }

View File

@ -35,10 +35,22 @@ impl DBOperator {
.fetch_one(&self.pool) .fetch_one(&self.pool)
.await .await
} }
pub async fn get_workers(&self) -> Result<Vec<Worker>, sqlx::Error> { pub async fn get_workers(
let pre_rets: Vec<WorkerRow> = sqlx::query_as::<_, WorkerRow>("SELECT * FROM `worker`") &self,
.fetch_all(&self.pool) start: i32,
.await?; length: i32,
only_not_fired: bool,
) -> Result<Vec<Worker>, sqlx::Error> {
let pre_rets: Vec<WorkerRow> = 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<Worker> = Vec::new(); let mut rets: Vec<Worker> = Vec::new();
let pos = self.get_position().await.unwrap(); let pos = self.get_position().await.unwrap();
for worker in pre_rets { for worker in pre_rets {
@ -84,23 +96,33 @@ impl DBOperator {
&self, &self,
start: i32, start: i32,
count: i32, count: i32,
without_written_offs: bool,
) -> Result<Vec<Equipment>, sqlx::Error> { ) -> Result<Vec<Equipment>, sqlx::Error> {
let pre_rets = let pre_rets = match without_written_offs {
sqlx::query_as::<_, EquipmentRow>("SELECT * FROM `equipment` LIMIT ? OFFSET ?") true => {
sqlx::query_as::<_, EquipmentRow>(
"SELECT * FROM `equipment` WHERE is_written_off=0 LIMIT ? OFFSET ? ",
)
.bind(count) .bind(count)
.bind(start) .bind(start)
.fetch_all(&self.pool) .fetch_all(&self.pool)
.await?; .await?
let mut rets: Vec<Equipment> = 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;
}
} }
false => {
sqlx::query_as::<_, EquipmentRow>("SELECT * FROM `equipment` LIMIT ? OFFSET ?")
.bind(count)
.bind(start)
.fetch_all(&self.pool)
.await?
}
};
let mut rets: Vec<Equipment> = 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 { rets.push(Equipment {
id: eq.id, id: eq.id,
name: eq.name, name: eq.name,
@ -186,26 +208,57 @@ impl DBOperator {
} }
Ok(rets) Ok(rets)
} }
pub async fn get_promotions(
&self,
start: i32,
length: i32,
) -> Result<Vec<Promotion>, 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<Promotion> = 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( pub async fn promote_worker(
&self, &self,
worker: Worker, worker: Worker,
position_to: Position, position_from: Position,
date: DateTime<chrono::Local>, date: DateTime<chrono::Local>,
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
let mut tx = self.pool.begin().await?; 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=?;") sqlx::query("UPDATE worker SET position_id=? WHERE id=?;")
.bind(position_to.id) .bind(worker.position.id)
.bind(worker.id) .bind(worker.id)
.execute(&mut *tx) .execute(&mut *tx)
.await?; .await?;
tx.commit().await tx.commit().await
} }
pub async fn add_worker(&self, worker: WorkerRow) -> Result<bool, sqlx::Error> { pub async fn add_worker(&self, worker: Worker) -> Result<bool, sqlx::Error> {
// 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) // 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, ?, ?, ?, ?);") 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.hire_date)
.bind(worker.is_fired) .bind(worker.is_fired)
.bind(worker.full_name) .bind(worker.full_name)
@ -291,17 +344,14 @@ impl DBOperator {
} }
} }
pub async fn update_worker(&self, worker: WorkerRow) -> Result<bool, sqlx::Error> { pub async fn update_worker(&self, worker: Worker) -> Result<bool, sqlx::Error> {
match sqlx::query( match sqlx::query("UPDATE `worker` SET hire_date=?, is_fired=?, full_name=? WHERE id=?;")
"UPDATE `worker` SET position_id=?, hire_date=?, is_fired=?, full_name=? WHERE id=?;", .bind(worker.hire_date)
) .bind(worker.is_fired)
.bind(worker.position_id) .bind(worker.full_name)
.bind(worker.hire_date) .bind(worker.id)
.bind(worker.is_fired) .execute(&self.pool)
.bind(worker.full_name) .await
.bind(worker.id)
.execute(&self.pool)
.await
{ {
Ok(_) => Ok(true), Ok(_) => Ok(true),
Err(_) => Ok(false), Err(_) => Ok(false),
@ -401,14 +451,15 @@ impl DBOperator {
Err(_) => Ok(false), Err(_) => Ok(false),
} }
} }
pub async fn get_salaries(&self) -> Result<Vec<Salary>, sqlx::Error> { pub async fn get_salaries(&self, start: i32, length: i32) -> Result<Vec<Salary>, sqlx::Error> {
let sals = sqlx::query_as::<_, SalaryRow>("SELECT * FROM `salary`") let sals = sqlx::query_as::<_, SalaryRow>("SELECT * FROM `salary` LIMIT ? OFFSET ?")
.bind(length)
.bind(start)
.fetch_all(&self.pool) .fetch_all(&self.pool)
.await?; .await?;
let mut rets: Vec<Salary> = Vec::new(); let mut rets: Vec<Salary> = Vec::new();
let workers = self.get_workers().await.unwrap();
for sal in sals { 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 { Some(w) => rets.push(Salary {
id: sal.id, id: sal.id,
worker: w.clone(), worker: w.clone(),
@ -466,6 +517,25 @@ impl DBOperator {
Err(_) => Ok(false), 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<RecipeElement> = 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<Vec<Recipe>, sqlx::Error> { pub async fn get_recipes(&self) -> Result<Vec<Recipe>, sqlx::Error> {
let pre_ret = sqlx::query_as::<_, RecipeRow>("SELECT * FROM `recipe`") let pre_ret = sqlx::query_as::<_, RecipeRow>("SELECT * FROM `recipe`")
.fetch_all(&self.pool) .fetch_all(&self.pool)
@ -582,6 +652,78 @@ impl DBOperator {
Err(_) => Ok(false), Err(_) => Ok(false),
} }
} }
pub async fn get_orders(
&self,
start: i32,
length: i32,
is_incoming: bool,
) -> Vec<Order>{
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<Order> = 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<OrderElement> {
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<OrderElement> = 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<Client, sqlx::Error> {
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> { pub async fn estabilish_connection() -> Result<(), sqlx::Error> {

View File

@ -4,7 +4,7 @@ mod app;
mod database; mod database;
mod material_tab; mod material_tab;
mod models; mod models;
mod production_tab; mod order_tab;
mod recipe_tab; mod recipe_tab;
mod reports; mod reports;
mod tab; mod tab;
@ -16,8 +16,8 @@ fn main() {
database::estabilish_connection(); database::estabilish_connection();
let mut native_opts = eframe::NativeOptions { let mut native_opts = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default() viewport: egui::ViewportBuilder::default()
.with_inner_size([815.0, 300.0]) .with_inner_size([815.0, 400.0])
.with_min_inner_size([750.0, 200.0]), .with_min_inner_size([750.0, 350.0]),
..Default::default() ..Default::default()
}; };
native_opts.renderer = eframe::Renderer::Glow; native_opts.renderer = eframe::Renderer::Glow;

View File

@ -3,6 +3,8 @@ use std::str::FromStr;
use bigdecimal::ToPrimitive; use bigdecimal::ToPrimitive;
use chrono::Datelike; use chrono::Datelike;
pub const ELEMENTS_PER_PAGE: i32 = 5;
pub fn naivedate_to_jiff(date: chrono::NaiveDate) -> jiff::civil::Date { pub fn naivedate_to_jiff(date: chrono::NaiveDate) -> jiff::civil::Date {
jiff::civil::Date::new( jiff::civil::Date::new(
date.year().to_i16().unwrap(), date.year().to_i16().unwrap(),

View File

@ -121,29 +121,53 @@ pub struct RecipeRow {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
} }
#[derive(FromRow)]
pub struct Client { pub struct Client {
id: i32, pub id: i32,
name: String, pub name: String,
address: String, pub address: String,
} }
pub struct Order<'a> { pub struct Order {
id: i32, pub id: i32,
client: Client, pub client: Client,
order_date: chrono::DateTime<chrono::Local>, pub order_date: chrono::DateTime<chrono::Local>,
total_amount: rust_decimal::Decimal, pub total_price: BigDecimal,
elements: std::vec::Vec<OrderElement<'a>>, pub elements: std::vec::Vec<OrderElement>,
} }
pub struct OrderElement<'a> { #[derive(FromRow)]
id: i32, pub struct OrderRow {
product: Product<'a>, pub id: i32,
quantity: i32, pub client_id: i32,
total_amount: rust_decimal::Decimal, pub order_date: chrono::DateTime<chrono::Local>,
pub total_price: BigDecimal,
} }
pub struct Product<'a> { pub struct OrderElement {
id: i32, pub id: i32,
recipe: &'a Recipe, pub product: Product,
volume: f64, pub quantity: i32,
price_per_unit: rust_decimal::Decimal, 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)] #[derive(Default)]
pub struct User { pub struct User {
@ -151,11 +175,22 @@ pub struct User {
is_admin: bool, is_admin: bool,
worker: Worker, worker: Worker,
} }
#[derive(Default)]
pub struct Promotion { pub struct Promotion {
pub id: i32,
pub worker: Worker, pub worker: Worker,
pub from_position: Position,
pub to_position: Position, pub to_position: Position,
pub date: DateTime<Local>, pub date: DateTime<Local>,
} }
#[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<Local>,
}
#[derive(Default)] #[derive(Default)]
pub struct EquipmentModalState { pub struct EquipmentModalState {
@ -165,6 +200,14 @@ pub struct EquipmentModalState {
pub status: ModalStatus, pub status: ModalStatus,
pub filters: EquipmentFilter, 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)] #[derive(Default)]
pub struct ModalWinState { pub struct ModalWinState {

112
code/src/order_tab.rs Normal file
View File

@ -0,0 +1,112 @@
use std::sync::Arc;
use egui::mutex::RwLock;
use egui_extras::Column;
use crate::{
database::DBOperator,
misc,
models::Order,
tab::{TABS_CAN_BE_WINDOWS, Tab, TabTypes},
};
pub struct OrderTabViewer {
db_oper: DBOperator,
rt: tokio::runtime::Runtime,
incoming_orders: Arc<RwLock<Vec<Order>>>,
outcoming_orders: Arc<RwLock<Vec<Order>>>,
incoming_order_page: i32,
outcoming_order_page: i32,
}
impl OrderTabViewer {
pub fn show_orders(&mut self, ui: &mut egui::Ui, incoming_orders: bool) {
let orders = if incoming_orders {
&self.incoming_orders
} else {
&self.outcoming_orders
};
ui.horizontal(|ui| {
if ui.button("Добавить").clicked() {}
if ui.button("<-").clicked() {}
ui.monospace(if incoming_orders {
self.incoming_order_page.to_string()
} else {
self.outcoming_order_page.to_string()
});
if ui.button("->").clicked() {}
});
egui_extras::TableBuilder::new(ui)
.columns(Column::auto(), 5)
.striped(true)
.vscroll(true)
.header(25.0, |mut heading| {
heading.col(|ui| {
ui.heading(if incoming_orders {
"Заказчик"
} else {
"Клиент"
});
});
heading.col(|ui| {
ui.heading("Состав");
});
heading.col(|ui| {
ui.heading("Стоимость");
});
heading.col(|ui| {
ui.heading("Дата создания");
});
});
}
}
impl egui_dock::TabViewer for OrderTabViewer {
type Tab = Tab;
fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
tab.title.clone().into()
}
fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) {
match tab.tab_type {
TabTypes::IncomingOrderList => {
self.show_orders(ui, true);
}
TabTypes::OutcomingOrderList => {
self.show_orders(ui, false);
}
_ => {
ui.label("Nope");
}
}
}
fn allowed_in_windows(&self, _tab: &mut Self::Tab) -> bool {
TABS_CAN_BE_WINDOWS
}
fn is_closeable(&self, _tab: &Self::Tab) -> bool {
false
}
}
impl Default for OrderTabViewer {
fn default() -> Self {
let rt = tokio::runtime::Runtime::new().unwrap();
let db_oper = rt.block_on(async { DBOperator::new().await });
Self {
incoming_orders: Arc::new(RwLock::new(
rt.block_on(async { db_oper.get_orders(0, misc::ELEMENTS_PER_PAGE, true).await }),
)),
outcoming_orders: Arc::new(RwLock::new(
rt.block_on(async { db_oper.get_orders(0, misc::ELEMENTS_PER_PAGE, false).await }),
)),
incoming_order_page: 0,
outcoming_order_page: 0,
db_oper,
rt,
}
}
}

View File

@ -1 +0,0 @@
struct ProductionTabViewer {}

View File

@ -302,7 +302,7 @@ impl Default for RecipeTabViewer {
rt.block_on(async { db_oper.get_materials().await.unwrap_or(Vec::new()) }), rt.block_on(async { db_oper.get_materials().await.unwrap_or(Vec::new()) }),
)), )),
equipment: Arc::new(RwLock::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 { process_recipe_status: RecipeModalState {

View File

@ -18,6 +18,8 @@ pub enum TabTypes {
RecipeList, RecipeList,
RecipeDetail, RecipeDetail,
ProductList, ProductList,
IncomingOrderList,
OutcomingOrderList,
} }
pub struct Tab { pub struct Tab {

View File

@ -6,20 +6,26 @@ use std::default::Default;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use crate::misc::ELEMENTS_PER_PAGE;
use crate::database::*; use crate::models::{
use crate::models::{ModalStatus, ModalWinState, Position, Worker, WorkerRow}; ModalStatus, ModalWinState, Position, Promotion, Worker, WorkerModalState, WorkerRow,
};
use crate::tab::*; use crate::tab::*;
use crate::{database::*, misc};
pub struct WorkerTabViewer { pub struct WorkerTabViewer {
db_oper: DBOperator, db_oper: DBOperator,
workers: Arc<RwLock<Vec<Worker>>>, workers: Arc<RwLock<Vec<Worker>>>,
positions: Arc<RwLock<Vec<Position>>>, positions: Arc<RwLock<Vec<Position>>>,
promotions: Arc<RwLock<Vec<Promotion>>>,
rt: tokio::runtime::Runtime, rt: tokio::runtime::Runtime,
process_worker_state: ModalWinState, process_worker_state: WorkerModalState,
process_position_state: ModalWinState, process_position_state: ModalWinState,
is_admin: bool, is_admin: bool,
only_not_fired_workers: bool,
workers_page: i32,
} }
impl WorkerTabViewer { impl WorkerTabViewer {
@ -30,10 +36,16 @@ impl WorkerTabViewer {
} }
} }
fn update_workers(&mut self) { fn update_workers(&mut self) {
self.workers = Arc::new(RwLock::new( self.workers = Arc::new(RwLock::new(self.rt.block_on(async {
self.rt self.db_oper
.block_on(async { self.db_oper.get_workers().await.unwrap() }), .get_workers(
)); self.workers_page,
ELEMENTS_PER_PAGE,
self.only_not_fired_workers,
)
.await
.unwrap()
})));
} }
fn update_positions(&mut self) { fn update_positions(&mut self) {
self.positions = Arc::new(RwLock::new( self.positions = Arc::new(RwLock::new(
@ -46,28 +58,14 @@ impl WorkerTabViewer {
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("Добавить").clicked() { if ui.button("Добавить").clicked() {
self.process_worker_state.is_open = true; self.process_worker_state.is_open = true;
self.process_worker_state self.process_worker_state.worker = Worker::default();
.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());
}; };
if ui.button("Обновить").clicked() { if ui.button("Обновить").clicked() {
self.update_workers(); self.update_workers();
} }
}); });
if self.process_worker_state.is_open { 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| { 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)) if ui.button("Закрыть").clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape))
{ {
@ -78,34 +76,25 @@ impl WorkerTabViewer {
ui.label("Имя:"); ui.label("Имя:");
ui.add( ui.add(
egui::TextEdit::singleline( egui::TextEdit::singleline(
self.process_worker_state.data.get_mut("full_name").unwrap(), &mut self.process_worker_state.worker.full_name,
) )
.hint_text("Иванов Иван Иванович"), .hint_text("Иванов Иван Иванович"),
); );
ui.end_row(); ui.end_row();
ui.label("Должность:"); 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", "") 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| { .show_ui(ui, |ui| {
for pos in self.positions.read().clone().iter() { for pos in self.positions.read().clone().iter() {
ui.selectable_value( ui.selectable_value(
self.process_worker_state &mut self.process_worker_state.worker.position,
.data pos.clone(),
.get_mut("position_id") &pos.name,
.unwrap(),
pos.id.to_string(),
pos.name.clone(),
); );
} }
}); });
@ -114,34 +103,31 @@ impl WorkerTabViewer {
let mut size = ui.spacing().interact_size; let mut size = ui.spacing().interact_size;
size.x = 200.0; size.x = 200.0;
ui.label("Дата найма:"); ui.label("Дата найма:");
ui.add_sized( let ndate = self.process_worker_state.worker.hire_date.date_naive();
size, let mut date = misc::naivedate_to_jiff(ndate);
egui::TextEdit::singleline( ui.add(egui_extras::DatePickerButton::new(&mut date));
self.process_worker_state.data.get_mut("hire_date").unwrap(), if misc::naivedate_to_jiff(
) self.process_worker_state.worker.hire_date.date_naive(),
.hint_text("1970-01-23"), ) != 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(); ui.end_row();
if self.process_worker_state.status == ModalStatus::Edit { if self.process_worker_state.status == ModalStatus::Edit {
ui.label("Уволен:"); ui.label("Уволен:");
let data = self.process_worker_state.data.get("is_fired").unwrap(); ui.checkbox(&mut self.process_worker_state.worker.is_fired, "");
let mut checked = data.parse::<bool>().unwrap();
ui.checkbox(&mut checked, "");
if checked.to_string() != data.clone() {
self.process_worker_state
.data
.insert("is_fired".to_string(), checked.to_string());
}
} }
}); });
self.process_worker_state.can_finish = self.check_date( self.process_worker_state.can_finish =
self.process_worker_state self.process_worker_state.worker.full_name.len() > 0
.data && self.process_worker_state.worker.position.id != 0;
.get("hire_date")
.unwrap()
.clone(),
);
} }
let btext = match self.process_worker_state.status { let btext = match self.process_worker_state.status {
ModalStatus::Add => "Добавить", ModalStatus::Add => "Добавить",
@ -160,91 +146,27 @@ impl WorkerTabViewer {
ModalStatus::Add => { ModalStatus::Add => {
self.rt.block_on(async { self.rt.block_on(async {
self.db_oper self.db_oper
.add_worker(WorkerRow { .add_worker(self.process_worker_state.worker.clone())
id: self
.process_worker_state
.data
.get("id")
.unwrap()
.parse::<i32>()
.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::<i32>()
.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,
})
.await .await
.ok() .ok();
}); });
} }
ModalStatus::Edit => { ModalStatus::Edit => {
self.rt.block_on(async { self.rt.block_on(async {
self.db_oper if self.process_worker_state.worker.position.id
.update_worker(WorkerRow { != self.process_worker_state.old_pos.id
id: self {
.process_worker_state self.db_oper
.data .promote_worker(
.get("id") self.process_worker_state.worker.clone(),
.unwrap() self.process_worker_state.old_pos.clone(),
.parse::<i32>() chrono::Local::now(),
.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::<i32>()
.unwrap(),
is_fired: self
.process_worker_state
.data
.get("is_fired")
.unwrap()
.parse::<bool>()
.unwrap(),
hire_date: chrono::NaiveDate::parse_from_str(
&self
.process_worker_state
.data
.get("hire_date")
.unwrap(),
"%Y-%m-%d",
) )
.unwrap() .await
.and_hms_opt(0, 0, 0) .unwrap();
.unwrap() }
.and_local_timezone(chrono::Local) self.db_oper
.unwrap(), .update_worker(self.process_worker_state.worker.clone())
})
.await .await
.ok(); .ok();
}); });
@ -294,25 +216,11 @@ impl WorkerTabViewer {
row.col(|ui| { row.col(|ui| {
ui.label(if wk.is_fired { "Да" } else { "Нет" }); 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.is_open = true;
self.process_worker_state.status = ModalStatus::Edit; self.process_worker_state.status = ModalStatus::Edit;
self.process_worker_state self.process_worker_state.worker = wk.clone();
.data self.process_worker_state.old_pos = wk.position.clone();
.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());
} }
}); });
} }
@ -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 { fn check_date(&self, date: String) -> bool {
chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d").is_ok() 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 { match &tab.tab_type {
TabTypes::WorkerList => self.show_worker(ui), TabTypes::WorkerList => self.show_worker(ui),
TabTypes::WorkerPosition => self.show_position(ui), TabTypes::WorkerPosition => self.show_position(ui),
TabTypes::WorkerPromotion => self.show_promotions(ui),
_ => { _ => {
ui.label("Nope"); ui.label("Nope");
} }
@ -544,8 +491,14 @@ impl Default for WorkerTabViewer {
let positions = Arc::new(RwLock::new( let positions = Arc::new(RwLock::new(
rt.block_on(async { db_oper.get_position().await.unwrap() }), rt.block_on(async { db_oper.get_position().await.unwrap() }),
)); ));
let workers = Arc::new(RwLock::new( let workers = Arc::new(RwLock::new(rt.block_on(async {
rt.block_on(async { db_oper.get_workers().await.unwrap() }), 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<String, String> = HashMap::new(); let mut pos_map: HashMap<String, String> = HashMap::new();
pos_map.extend(|| -> Vec<(String, String)> { pos_map.extend(|| -> Vec<(String, String)> {
@ -556,20 +509,7 @@ impl Default for WorkerTabViewer {
("wage".to_string(), pos.wage.to_string()), ("wage".to_string(), pos.wage.to_string()),
] ]
}()); }());
let mut worker_map: HashMap<String, String> = HashMap::new(); let worker = workers.read()[0].clone();
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()),
]
}());
Self { Self {
process_position_state: ModalWinState { process_position_state: ModalWinState {
is_open: false, is_open: false,
@ -577,18 +517,22 @@ impl Default for WorkerTabViewer {
data: pos_map, data: pos_map,
status: ModalStatus::Add, status: ModalStatus::Add,
}, },
process_worker_state: ModalWinState { process_worker_state: WorkerModalState {
is_open: false, is_open: false,
can_finish: false, can_finish: false,
data: worker_map, worker,
status: ModalStatus::Add, status: ModalStatus::Add,
old_pos: Position::default(),
}, },
workers, workers,
positions: positions, positions,
promotions,
db_oper, db_oper,
rt, rt,
is_admin: false, is_admin: false,
only_not_fired_workers: false,
workers_page: 0,
} }
} }
} }