parent
6f784d149b
commit
41e4bcb2d6
|
|
@ -185,16 +185,22 @@ struct MainTabViewer {
|
||||||
salaries: Arc<RwLock<Vec<Salary>>>,
|
salaries: Arc<RwLock<Vec<Salary>>>,
|
||||||
|
|
||||||
db_oper: DBOperator,
|
db_oper: DBOperator,
|
||||||
|
rt: tokio::runtime::Runtime,
|
||||||
|
|
||||||
worker_tabs: WorkerTabViewer,
|
worker_tabs: WorkerTabViewer,
|
||||||
worker_tree: egui_dock::DockState<Tab>,
|
worker_tree: egui_dock::DockState<Tab>,
|
||||||
material_tabs: MaterialTabViewer,
|
material_tabs: MaterialTabViewer,
|
||||||
material_tree: egui_dock::DockState<Tab>,
|
material_tree: egui_dock::DockState<Tab>,
|
||||||
recipe_tab: RecipeTabViewer,
|
recipe_tab: RecipeTabViewer,
|
||||||
rt: tokio::runtime::Runtime,
|
|
||||||
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,
|
||||||
|
elements_per_page: i32,
|
||||||
|
|
||||||
is_admin: bool,
|
is_admin: bool,
|
||||||
reporter: Reporter,
|
reporter: Reporter,
|
||||||
}
|
}
|
||||||
|
|
@ -202,13 +208,14 @@ impl Default for MainTabViewer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
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;
|
||||||
Self {
|
Self {
|
||||||
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(
|
equipment: Arc::new(RwLock::new(rt.block_on(async {
|
||||||
rt.block_on(async { db_oper.get_equipment().await.unwrap() }),
|
db_oper.get_equipment(0, elements_per_page).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() }),
|
||||||
)),
|
)),
|
||||||
|
|
@ -223,6 +230,10 @@ impl Default for MainTabViewer {
|
||||||
title: "Должности".to_owned(),
|
title: "Должности".to_owned(),
|
||||||
tab_type: TabTypes::WorkerPosition,
|
tab_type: TabTypes::WorkerPosition,
|
||||||
},
|
},
|
||||||
|
Tab {
|
||||||
|
title: "Повышения".to_owned(),
|
||||||
|
tab_type: TabTypes::WorkerPromotion,
|
||||||
|
},
|
||||||
]),
|
]),
|
||||||
material_tabs: MaterialTabViewer::default(),
|
material_tabs: MaterialTabViewer::default(),
|
||||||
material_tree: egui_dock::DockState::new(vec![
|
material_tree: egui_dock::DockState::new(vec![
|
||||||
|
|
@ -261,6 +272,9 @@ impl Default for MainTabViewer {
|
||||||
},
|
},
|
||||||
is_admin: false,
|
is_admin: false,
|
||||||
reporter: Reporter {},
|
reporter: Reporter {},
|
||||||
|
|
||||||
|
elements_per_page,
|
||||||
|
page: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -272,10 +286,12 @@ impl MainTabViewer {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
fn update_equipment(&mut self) {
|
fn update_equipment(&mut self) {
|
||||||
self.equipment = Arc::new(RwLock::new(
|
self.equipment = Arc::new(RwLock::new(self.rt.block_on(async {
|
||||||
self.rt
|
self.db_oper
|
||||||
.block_on(async { self.db_oper.get_equipment().await.unwrap() }),
|
.get_equipment(self.page, self.elements_per_page)
|
||||||
));
|
.await
|
||||||
|
.unwrap()
|
||||||
|
})));
|
||||||
}
|
}
|
||||||
fn get_salary(&self) -> Salary {
|
fn get_salary(&self) -> Salary {
|
||||||
Salary {
|
Salary {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@ impl DBOperator {
|
||||||
.await?;
|
.await?;
|
||||||
Ok(rets)
|
Ok(rets)
|
||||||
}
|
}
|
||||||
|
pub async fn get_position_by_id(&self, id: i32) -> Result<Position, sqlx::Error> {
|
||||||
|
sqlx::query_as::<_, Position>("SELECT * FROM `position` WHERE id = ?")
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
pub async fn get_workers(&self) -> Result<Vec<Worker>, sqlx::Error> {
|
pub async fn get_workers(&self) -> Result<Vec<Worker>, sqlx::Error> {
|
||||||
let pre_rets: Vec<WorkerRow> = sqlx::query_as::<_, WorkerRow>("SELECT * FROM `worker`")
|
let pre_rets: Vec<WorkerRow> = sqlx::query_as::<_, WorkerRow>("SELECT * FROM `worker`")
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
|
|
@ -49,14 +55,42 @@ impl DBOperator {
|
||||||
hire_date: worker.hire_date,
|
hire_date: worker.hire_date,
|
||||||
position: ppos,
|
position: ppos,
|
||||||
is_fired: worker.is_fired,
|
is_fired: worker.is_fired,
|
||||||
|
fire_date: worker.fire_date,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(rets)
|
Ok(rets)
|
||||||
}
|
}
|
||||||
pub async fn get_equipment(&self) -> Result<Vec<Equipment>, sqlx::Error> {
|
pub async fn get_worker_by_id(&self, id: i32) -> Result<Worker, sqlx::Error> {
|
||||||
let pre_rets = sqlx::query_as::<_, EquipmentRow>("SELECT * FROM `equipment`")
|
match sqlx::query_as::<_, WorkerRow>("SELECT * FROM `worker` WHERE id = ?")
|
||||||
.fetch_all(&self.pool)
|
.bind(id)
|
||||||
.await?;
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(n) => Ok(Worker {
|
||||||
|
id: n.id,
|
||||||
|
full_name: n.full_name,
|
||||||
|
position: self
|
||||||
|
.get_position_by_id(n.position_id)
|
||||||
|
.await
|
||||||
|
.unwrap_or(Position::default()),
|
||||||
|
is_fired: n.is_fired,
|
||||||
|
hire_date: n.hire_date,
|
||||||
|
fire_date: n.fire_date,
|
||||||
|
}),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn get_equipment(
|
||||||
|
&self,
|
||||||
|
start: i32,
|
||||||
|
count: i32,
|
||||||
|
) -> Result<Vec<Equipment>, sqlx::Error> {
|
||||||
|
let pre_rets =
|
||||||
|
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();
|
let mut rets: Vec<Equipment> = Vec::new();
|
||||||
let workers = self.get_workers().await?;
|
let workers = self.get_workers().await?;
|
||||||
for eq in pre_rets {
|
for eq in pre_rets {
|
||||||
|
|
@ -78,6 +112,26 @@ impl DBOperator {
|
||||||
}
|
}
|
||||||
Ok(rets)
|
Ok(rets)
|
||||||
}
|
}
|
||||||
|
pub async fn get_equipment_by_id(&self, id: i32) -> Result<Equipment, sqlx::Error> {
|
||||||
|
match sqlx::query_as::<_, EquipmentRow>("SELECT * FROM `equipment` WHERE id = ?")
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(n) => Ok(Equipment {
|
||||||
|
id,
|
||||||
|
name: n.name,
|
||||||
|
inv_number: n.inv_number,
|
||||||
|
maintenance_date: n.maintenance_date,
|
||||||
|
worker: self
|
||||||
|
.get_worker_by_id(n.worker_id)
|
||||||
|
.await
|
||||||
|
.unwrap_or(Worker::default()),
|
||||||
|
is_written_off: n.is_written_off,
|
||||||
|
}),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub async fn add_equipment(&self, equipment: Equipment) -> Result<bool, sqlx::Error> {
|
pub async fn add_equipment(&self, equipment: Equipment) -> Result<bool, sqlx::Error> {
|
||||||
match sqlx::query("INSERT INTO `equipment` (id, inv_number, maintenance_date, worker_id, name, is_written_off) VALUES(0, ?, ?, ?, ?, 0);")
|
match sqlx::query("INSERT INTO `equipment` (id, inv_number, maintenance_date, worker_id, name, is_written_off) VALUES(0, ?, ?, ?, ?, 0);")
|
||||||
.bind(equipment.inv_number)
|
.bind(equipment.inv_number)
|
||||||
|
|
@ -132,19 +186,21 @@ impl DBOperator {
|
||||||
}
|
}
|
||||||
Ok(rets)
|
Ok(rets)
|
||||||
}
|
}
|
||||||
pub async fn check_worker(&self, worker: Worker) -> Result<bool, sqlx::Error> {
|
pub async fn promote_worker(
|
||||||
let ret = sqlx::query(&format!(
|
&self,
|
||||||
"SELECT * FROM `worker` WHERE full_name = {}, position_id = {}, hire_date = {}",
|
worker: Worker,
|
||||||
worker.full_name,
|
position_to: Position,
|
||||||
worker.position.id,
|
date: DateTime<chrono::Local>,
|
||||||
worker.hire_date.to_string()
|
) -> Result<(), sqlx::Error> {
|
||||||
))
|
let mut tx = self.pool.begin().await?;
|
||||||
.fetch_all(&self.pool)
|
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?;
|
||||||
.await?;
|
sqlx::query("UPDATE worker SET position_id=? WHERE id=?;")
|
||||||
if ret.len() > 0 {
|
.bind(position_to.id)
|
||||||
return Ok(true);
|
.bind(worker.id)
|
||||||
}
|
.execute(&mut *tx)
|
||||||
Ok(false)
|
.await?;
|
||||||
|
|
||||||
|
tx.commit().await
|
||||||
}
|
}
|
||||||
pub async fn add_worker(&self, worker: WorkerRow) -> Result<bool, sqlx::Error> {
|
pub async fn add_worker(&self, worker: WorkerRow) -> 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)
|
||||||
|
|
@ -236,20 +292,19 @@ impl DBOperator {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_worker(&self, worker: WorkerRow) -> Result<bool, sqlx::Error> {
|
pub async fn update_worker(&self, worker: WorkerRow) -> Result<bool, sqlx::Error> {
|
||||||
match sqlx::query("UPDATE `worker` SET position_id=?, hire_date=?, is_fired=?, full_name=? WHERE id=?;")
|
match sqlx::query(
|
||||||
|
"UPDATE `worker` SET position_id=?, hire_date=?, is_fired=?, full_name=? WHERE id=?;",
|
||||||
|
)
|
||||||
.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)
|
||||||
.bind(worker.id)
|
.bind(worker.id)
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
.await{
|
.await
|
||||||
Ok(_) =>{
|
{
|
||||||
Ok(true)
|
Ok(_) => Ok(true),
|
||||||
},
|
Err(_) => Ok(false),
|
||||||
Err(_)=>{
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn update_material_category(
|
pub async fn update_material_category(
|
||||||
|
|
@ -267,40 +322,18 @@ impl DBOperator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn update_material(&self, mat: MaterialRow) -> Result<bool, sqlx::Error> {
|
pub async fn update_material(&self, mat: MaterialRow) -> Result<bool, sqlx::Error> {
|
||||||
match sqlx::query(
|
match sqlx::query("UPDATE `material` SET name=?, quantity=?, category_id=? WHERE id=?;")
|
||||||
"UPDATE `material` SET name=?, quantity=?, category_id=? WHERE id=?;",
|
.bind(mat.name)
|
||||||
)
|
.bind(mat.quantity)
|
||||||
.bind(mat.name)
|
.bind(mat.category_id)
|
||||||
.bind(mat.quantity)
|
.bind(mat.id)
|
||||||
.bind(mat.category_id)
|
.execute(&self.pool)
|
||||||
.bind(mat.id)
|
.await
|
||||||
.execute(&self.pool)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
Ok(_) => Ok(true),
|
Ok(_) => Ok(true),
|
||||||
Err(_) => Ok(false),
|
Err(_) => Ok(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn get_worker_by_id(&self, worker_id: i32) -> Result<Worker, sqlx::Error> {
|
|
||||||
let preret = sqlx::query_as::<_, WorkerRow>("SELECT * FROM `worker` WHERE id = ?;")
|
|
||||||
.bind(worker_id)
|
|
||||||
.fetch_all(&self.pool)
|
|
||||||
.await?;
|
|
||||||
let poses = self.get_position().await?;
|
|
||||||
let ret = Ok(Worker {
|
|
||||||
id: preret[0].id,
|
|
||||||
full_name: preret[0].full_name.clone(),
|
|
||||||
hire_date: preret[0].hire_date,
|
|
||||||
is_fired: preret[0].is_fired,
|
|
||||||
position: poses
|
|
||||||
.iter()
|
|
||||||
.find(|&pos| &pos.id == &preret[0].position_id)
|
|
||||||
.unwrap()
|
|
||||||
.clone(),
|
|
||||||
});
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_position(&self, pos: Position) -> Result<bool, sqlx::Error> {
|
pub async fn update_position(&self, pos: Position) -> Result<bool, sqlx::Error> {
|
||||||
match sqlx::query("UPDATE `position`SET name=?, wage=? WHERE id=?")
|
match sqlx::query("UPDATE `position`SET name=?, wage=? WHERE id=?")
|
||||||
.bind(pos.name)
|
.bind(pos.name)
|
||||||
|
|
@ -408,16 +441,19 @@ impl DBOperator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn update_salary(&self, salary: Salary) -> Result<bool, sqlx::Error> {
|
pub async fn update_salary(&self, salary: Salary) -> Result<bool, sqlx::Error> {
|
||||||
match sqlx::query("UPDATE `salary` SET worker_id=?, salary_size=?, salary_date=?, comment=? WHERE id=?;")
|
match sqlx::query(
|
||||||
.bind(salary.worker.id)
|
"UPDATE `salary` SET worker_id=?, salary_size=?, salary_date=?, comment=? WHERE id=?;",
|
||||||
.bind(salary.salary_size)
|
)
|
||||||
.bind(salary.salary_date)
|
.bind(salary.worker.id)
|
||||||
.bind(salary.comment)
|
.bind(salary.salary_size)
|
||||||
.bind(salary.id)
|
.bind(salary.salary_date)
|
||||||
.execute(&self.pool)
|
.bind(salary.comment)
|
||||||
.await{
|
.bind(salary.id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(_) => Ok(true),
|
Ok(_) => Ok(true),
|
||||||
Err(_) => Ok(false)
|
Err(_) => Ok(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn remove_salary(&self, id: i32) -> Result<bool, sqlx::Error> {
|
pub async fn remove_salary(&self, id: i32) -> Result<bool, sqlx::Error> {
|
||||||
|
|
@ -490,14 +526,17 @@ impl DBOperator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 `recipe_element`;").fetch_all(&self.pool).await;
|
let pre_ret = sqlx::query_as::<_, RecipeElementRow>(
|
||||||
|
"SELECT id, recipe_id, material_id, equipment_id, quantity FROM `recipe_element`;",
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
let mats = self.get_materials().await.unwrap();
|
let mats = self.get_materials().await.unwrap();
|
||||||
let eqs = self.get_equipment().await.unwrap();
|
|
||||||
let mut ret: Vec<RecipeElement> = Vec::new();
|
let mut ret: Vec<RecipeElement> = Vec::new();
|
||||||
for re in pre_ret.unwrap().iter() {
|
for re in pre_ret.unwrap().iter() {
|
||||||
let mat = mats.iter().find(|m| m.id == re.material_id);
|
let mat = mats.iter().find(|m| m.id == re.material_id);
|
||||||
let eq = eqs.iter().find(|e| e.id == re.equipment_id);
|
let eq = self.get_equipment_by_id(re.equipment_id).await.ok();
|
||||||
match (mat, eq) {
|
match (mat, eq) {
|
||||||
(Some(m), Some(e)) => {
|
(Some(m), Some(e)) => {
|
||||||
ret.push(RecipeElement {
|
ret.push(RecipeElement {
|
||||||
|
|
|
||||||
|
|
@ -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([700.0, 300.0])
|
.with_inner_size([815.0, 300.0])
|
||||||
.with_min_inner_size([650.0, 200.0]),
|
.with_min_inner_size([750.0, 200.0]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
native_opts.renderer = eframe::Renderer::Glow;
|
native_opts.renderer = eframe::Renderer::Glow;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ pub struct Worker {
|
||||||
pub hire_date: DateTime<chrono::Local>,
|
pub hire_date: DateTime<chrono::Local>,
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub is_fired: bool,
|
pub is_fired: bool,
|
||||||
|
pub fire_date: Option<DateTime<chrono::Local>>,
|
||||||
}
|
}
|
||||||
#[derive(sqlx::FromRow, sqlx::Decode)]
|
#[derive(sqlx::FromRow, sqlx::Decode)]
|
||||||
pub struct WorkerRow {
|
pub struct WorkerRow {
|
||||||
|
|
@ -37,6 +38,7 @@ pub struct WorkerRow {
|
||||||
pub hire_date: DateTime<chrono::Local>,
|
pub hire_date: DateTime<chrono::Local>,
|
||||||
pub position_id: i32,
|
pub position_id: i32,
|
||||||
pub is_fired: bool,
|
pub is_fired: bool,
|
||||||
|
pub fire_date: Option<DateTime<chrono::Local>>,
|
||||||
}
|
}
|
||||||
#[derive(sqlx::FromRow, sqlx::Decode)]
|
#[derive(sqlx::FromRow, sqlx::Decode)]
|
||||||
pub struct SalaryRow {
|
pub struct SalaryRow {
|
||||||
|
|
|
||||||
|
|
@ -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().await.unwrap_or(Vec::new()) }),
|
rt.block_on(async { db_oper.get_equipment(0,5).await.unwrap_or(Vec::new()) }),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
process_recipe_status: RecipeModalState {
|
process_recipe_status: RecipeModalState {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ pub static TABS_CAN_BE_WINDOWS: bool = false;
|
||||||
pub enum TabTypes {
|
pub enum TabTypes {
|
||||||
Equipment,
|
Equipment,
|
||||||
Worker,
|
Worker,
|
||||||
Position,
|
|
||||||
Salary,
|
Salary,
|
||||||
Material,
|
Material,
|
||||||
Recipe,
|
Recipe,
|
||||||
|
|
@ -13,6 +12,7 @@ pub enum TabTypes {
|
||||||
Settings,
|
Settings,
|
||||||
WorkerList,
|
WorkerList,
|
||||||
WorkerPosition,
|
WorkerPosition,
|
||||||
|
WorkerPromotion,
|
||||||
MaterialList,
|
MaterialList,
|
||||||
MaterialTypeList,
|
MaterialTypeList,
|
||||||
RecipeList,
|
RecipeList,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue