extern crate diesel;
use crate::{models::*, *};
use prettytable::Table;
use anyhow::anyhow;
use self::diesel::prelude::*;
use std::io::{BufReader, BufWriter};
use std::{fmt::Debug, fs::File};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[derive(Debug, Deserialize)]
struct NewInventoryRecord {
mpn: String,
quantity: Option<i32>,
notes: Option<String>,
unit_price: Option<f32>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct InventoryEntry {
pub id: i32,
pub mpn: String,
pub quantity: i32,
pub consumed: i32,
pub unit_price: Option<f32>,
pub notes: Option<String>,
pub part_ver: i32,
pub part_id: i32,
}
#[derive(Debug, Serialize)]
pub struct Shortage {
pub pid: i32,
pub pn: String,
pub mpn: String,
pub desc: String,
pub have: i32,
pub needed: i32,
pub short: i32,
pub quantity: Option<i32>,
pub notes: Option<String>,
pub unit_price: Option<f32>,
}
fn read_records<T>(filename: &str) -> anyhow::Result<Vec<T>>
where
T: DeserializeOwned + Debug,
{
let file = File::open(filename).unwrap();
let file = BufReader::new(file);
let mut records: Vec<T> = Vec::new();
let mut rdr = csv::Reader::from_reader(file);
for (pos, result) in rdr.deserialize().enumerate() {
let record: T = match result {
Ok(r) => r,
Err(e) => return Err(anyhow!("Unable to process line {}. Error: {}", pos, e)),
};
println!("Processing: {:?}", record);
records.push(record);
}
Ok(records)
}
pub fn update_from_file(app: &mut crate::Application, filename: &str) {
let records: Vec<InventoryEntry> = match read_records(filename) {
Ok(r) => r,
Err(e) => {
eprintln!("{}\nNo changes have been made", e);
return;
}
};
for record in &records {
let notes = record.notes.as_deref();
let update = NewUpdateInventoryEntry {
quantity: &record.quantity,
consumed: &record.consumed,
unit_price: record.unit_price.as_ref(),
notes,
part_ver: &record.part_ver,
part_id: &record.part_id,
};
if let Err(e) = update_inventory_by_id(&app.conn, &record.id, &update) {
eprintln!("Error updating inventory id: {}. Error: {}", record.id, e);
} else {
println!("Updated: {}", record.mpn);
}
}
}
pub fn create_from_file(app: &mut crate::Application, filename: &str) {
println!("{:?}", app.config);
println!("{:?}", filename);
let records: Vec<NewInventoryRecord> = match read_records(filename) {
Ok(r) => r,
Err(e) => {
eprintln!("{}\nNo changes have been made", e);
return;
}
};
for record in &records {
println!("Finding: \"{}\"", record.mpn);
let part = find_part_by_mpn(&app.conn, &record.mpn);
match part {
Err(e) => {
println!(
"{} was not found! No changes were made. Error: {}",
record.mpn, e
);
std::process::exit(1);
}
_ => {
continue;
}
}
}
for record in &records {
let quantity = match record.quantity {
Some(q) => q,
None => continue,
};
let notes = record.notes.as_deref();
let part = find_part_by_mpn(&app.conn, &record.mpn).expect("Unable to get part.");
let entry = NewUpdateInventoryEntry {
part_id: &part.id,
part_ver: &part.ver,
unit_price: record.unit_price.as_ref(),
quantity: &quantity,
consumed: &0,
notes,
};
create_inventory(&app.conn, &entry).expect("Unable to create inventory item.");
println!("Created inventory for {}!", part.pn);
}
}
pub fn create(app: &mut crate::Application) {
let part_number = app.prompt.ask_text_entry("Enter part number: ");
let part = find_part_by_pn(&app.conn, &part_number);
let part = match part {
Ok(x) => x,
Err(_) => {
println!("Unable to find {}", part_number);
std::process::exit(1);
}
};
let adj = app.prompt.ask_text_entry("Enter adjustment value: ");
let adj: i32 = adj.trim().parse().expect("Invalid adjustment!");
let price = app.prompt.ask_text_entry("Enter unit price: ");
let price: f32 = price.trim().parse().expect("Invalid price!");
let notes = app.prompt.ask_text_entry("Enter notes: ");
println!("Part number: {}", part.pn);
println!("Ajustment: {}", adj);
println!("Price: ${}", price);
println!("Notes: {}", notes);
let proceed = app.prompt.ask_yes_no_question("Look ok?");
if proceed {
let entry = NewUpdateInventoryEntry {
part_id: &part.id,
part_ver: &part.ver,
unit_price: Some(&price),
quantity: &adj,
consumed: &0,
notes: Some(¬es),
};
create_inventory(&app.conn, &entry).expect("Unable to create inventory item.");
}
}
pub fn show(app: &mut crate::Application, show_all_entries: bool) {
use crate::schema::inventories::dsl::*;
let mut table = Table::new();
let results = inventories
.load::<Inventory>(&app.conn)
.expect("Error loading parts");
table.add_row(row![
"PN",
"Desc",
"Qty",
"Consumed",
"Unit Price",
"Notes",
"Ver"
]);
for inventory in results {
if !show_all_entries && inventory.quantity == 0 {
continue;
}
let part = find_part_by_id(&app.conn, &inventory.part_id).expect("Unable to get part.");
table.add_row(row![
part.pn,
part.descr,
inventory.quantity,
inventory.consumed,
inventory.unit_price.unwrap_or(0.0),
inventory.notes.unwrap_or_else(|| "".to_string()),
inventory.part_ver
]);
}
if table.len() == 1 {
println!("No inventory to display.");
} else {
println!("Displaying {} parts", table.len() - 1);
table.printstd();
}
}
pub fn show_shortage(app: &mut crate::Application, show_all_entries: bool) {
let mut table = Table::new();
table.add_row(row!["PID", "PN", "MPN", "Desc", "Have", "Needed", "Short",]);
let shortages = get_shortages(app, show_all_entries);
let shortages = match shortages {
Ok(x) => x,
Err(e) => {
println!("Error getting shortages: {:?}", e);
std::process::exit(1);
}
};
for entry in shortages {
table.add_row(row![
entry.pid,
entry.pn,
entry.mpn,
entry.desc,
entry.have,
entry.needed,
entry.short,
]);
}
table.printstd();
}
pub fn export_to_file(app: &mut crate::Application, filename: &str, export_all: bool) {
use crate::schema::*;
let inventory = inventories::dsl::inventories
.load::<Inventory>(&app.conn)
.expect("Uanble to load inventory list.");
let file = File::create(filename).unwrap();
let file = BufWriter::new(file);
let mut wtr = csv::Writer::from_writer(file);
for entry in inventory {
if !export_all && entry.quantity == 0 {
continue;
}
let part = find_part_by_id(&app.conn, &entry.part_id).unwrap();
let inventory_entry = InventoryEntry {
id: entry.id,
mpn: part.mpn,
quantity: entry.quantity,
consumed: entry.consumed,
unit_price: entry.unit_price,
notes: entry.notes,
part_ver: entry.part_ver,
part_id: entry.part_id,
};
wtr.serialize(inventory_entry)
.expect("Unable to serialize.");
wtr.flush().expect("Unable to flush");
}
println!("Inventory list exported to {}", filename);
}
pub fn export_shortages_to_file(app: &mut crate::Application, filename: &str) {
let shortages = get_shortages(app, false).expect("Unable to get shortage report.");
let file = File::create(filename).unwrap();
let file = BufWriter::new(file);
let mut wtr = csv::Writer::from_writer(file);
for shortage in shortages {
wtr.serialize(shortage).expect("Unable to serialize.");
wtr.flush().expect("Unable to flush");
}
println!("Shortages exported to {}", filename);
}
pub fn get_shortages(
app: &mut crate::Application,
show_all_entries: bool,
) -> std::result::Result<Vec<Shortage>, diesel::result::Error> {
use crate::schema::*;
let results = builds::dsl::builds
.filter(builds::dsl::complete.eq(0)) .load::<Build>(&app.conn);
let results = match results {
Ok(x) => x,
Err(e) => return Err(e),
};
let mut shortages: Vec<Shortage> = Vec::new();
for build in results {
let bom_list = parts_parts::dsl::parts_parts
.filter(parts_parts::dsl::bom_part_id.eq(build.part_id))
.filter(parts_parts::dsl::bom_ver.eq(build.part_ver))
.load::<PartsPart>(&app.conn);
let bom_list = match bom_list {
Ok(x) => x,
Err(e) => return Err(e),
};
for bom_list_entry in bom_list {
if bom_list_entry.nostuff == 1 {
continue;
}
let mut inventory_quantity = 0;
let inventory_entries = find_inventories_by_part_id(&app.conn, &bom_list_entry.part_id);
let inventory_entries = match inventory_entries {
Ok(x) => x,
Err(e) => return Err(e),
};
for entry in inventory_entries {
inventory_quantity += entry.quantity;
}
let mut found_in_shortage_list = false;
for mut entry in &mut shortages {
if entry.pid == bom_list_entry.part_id {
let mut short = entry.needed + bom_list_entry.quantity - inventory_quantity;
if short < 0 {
short = 0;
}
entry.needed += build.quantity * bom_list_entry.quantity;
entry.short = short;
found_in_shortage_list = true;
break;
}
}
if !found_in_shortage_list {
let part = find_part_by_id(&app.conn, &bom_list_entry.part_id);
let part = match part {
Ok(x) => x,
Err(e) => return Err(e),
};
let mut short = (build.quantity * bom_list_entry.quantity) - inventory_quantity;
if short < 0 {
short = 0;
}
let shortage = Shortage {
pid: bom_list_entry.part_id,
pn: part.pn,
mpn: part.mpn,
desc: part.descr,
have: inventory_quantity,
needed: build.quantity * bom_list_entry.quantity,
short,
unit_price: None,
notes: None,
quantity: None,
};
shortages.push(shortage);
}
}
}
if !show_all_entries {
let mut only_shortages: Vec<Shortage> = Vec::new();
for shortage in shortages {
if shortage.short != 0 {
only_shortages.push(shortage);
}
}
Ok(only_shortages)
} else {
Ok(shortages)
}
}