extern crate diesel;
extern crate quick_xml;
extern crate serde;
use crate::schematic::VariantDef;
use crate::*;
use prettytable::Table;
use quick_xml::de::from_reader;
use self::diesel::prelude::*;
use chrono::Utc;
use serde::Serialize;
use std::io::{BufReader, BufWriter};
use std::{
fs::File,
io::{StdinLock, Stdout},
};
#[derive(Eq, PartialEq, Debug)]
struct LineItem {
name: String,
pn: String,
quantity: i32,
nostuff: i32,
}
#[derive(Debug, Default, Clone)]
struct SimplePart {
pn: String,
mpn: String,
descr: String,
ver: i32,
mqty: i32,
nostuff: i32,
}
#[derive(Serialize)]
struct BomEntry {
pn: String,
quantity: i32,
refdes: String,
mpn: String,
descr: String,
ver: i32,
inventory_qty: i32,
no_stuff: i32,
}
fn print_simple_part_list(list: &[SimplePart]) {
let mut table = Table::new();
table.add_row(row![
"PART NUMBER",
"MPN",
"DESCRIPTION",
"MULTI QUANTITY",
"VERSION",
"NO STUFF"
]);
for part in list {
table.add_row(row![
part.pn,
part.mpn,
part.descr,
part.mqty,
part.ver,
part.nostuff
]);
}
table.printstd();
}
fn get_simplepart_from_library(
item: &LineItem,
eagle: &schematic::Eagle,
library_name: &str,
) -> SimplePart {
let mut part = SimplePart {
pn: item.pn.clone(),
nostuff: item.nostuff,
mqty: item.quantity,
..Default::default()
};
for library in &eagle.drawing.schematic.libraries.library {
if library.name == library_name {
'outer: for deviceset in &library.devicesets.deviceset {
for device in &deviceset.devices.device {
for technology in &device.technologies.technology {
let library_part_number =
format!("{}{}{}", deviceset.name, technology.name, device.name);
if library_part_number == item.pn {
match &technology.attribute {
Some(attributes) => {
for attribute in attributes {
if attribute.value.is_empty() {
continue;
}
if attribute.name == "MPN" {
part.mpn = attribute.value.clone();
} else if attribute.name == "DIGIKEYPN" {
} else if attribute.name == "DESC" {
part.descr = attribute.value.clone();
} else if attribute.name == "MQTY" {
part.mqty = attribute
.value
.trim()
.parse()
.expect("Unable to convert mqty");
} else if attribute.name == "ALIAS" {
println!("Alias!");
}
}
}
None => (),
};
break 'outer;
}
}
}
}
};
}
part
}
fn get_line_items_from_parts(
parts: &[schematic::Part],
variant: &VariantDef,
ignore_list: &[String],
) -> Vec<LineItem> {
let mut list: Vec<LineItem> = Vec::new();
'outer: for part in parts {
for entry in ignore_list.iter() {
if part.deviceset.contains(entry) {
continue 'outer;
}
}
let mut technology = part.technology.clone().unwrap_or_default();
let mut nostuff = 0;
for var in &part.variants {
if var.name == variant.name {
technology = match &var.technology {
Some(t) => t.to_string(),
None => technology,
};
if var.populate == Some("no".to_string()) {
nostuff = 1;
}
}
}
let part_number = format!("{}{}{}", part.deviceset, technology, part.device,);
let item = LineItem {
name: part.name.clone(),
pn: part_number,
quantity: 1,
nostuff,
};
let mut found = false;
if let Some(entry) = list
.iter_mut()
.find(|part| part.pn == item.pn && part.nostuff == item.nostuff)
{
found = true;
entry.name = format!("{} {}", entry.name, item.name);
entry.quantity += 1;
}
if !found {
list.push(item);
}
}
list
}
fn prompt_to_update_part(
prompt: &mut prompt::Prompt<StdinLock, Stdout>,
new: &models::NewUpdatePart,
existing: &models::Part,
) -> bool {
if new.mpn != existing.mpn || new.descr != existing.descr || *new.ver != existing.ver {
let question = format!("{} found! Would you like to update it?", existing.pn);
let mut table = Table::new();
table.add_row(row!["", "pn", "mpn", "decr", "mqty", "ver"]);
table.add_row(row![
"Current:",
existing.pn,
existing.mpn,
existing.descr,
existing.mqty,
existing.ver
]);
table.add_row(row![
"Change to:",
new.pn,
new.mpn,
new.descr,
new.mqty,
new.ver
]);
table.printstd();
return prompt.ask_yes_no_question(&question);
}
false
}
pub fn import(app: &mut crate::Application, filename: &str) {
use crate::schema::parts::dsl::*;
let file = File::open(filename);
let file = match file {
Ok(x) => x,
Err(_) => {
println!("Unable to open {}", filename);
std::process::exit(1);
}
};
let file = BufReader::new(file);
let eagle: schematic::Eagle = from_reader(file).expect("error parsing xml");
let mut found = false;
let mut bom_pn = "".to_string();
let mut bom_desc = "".to_string();
let mut revision = 1;
for attribute in &eagle.drawing.schematic.attributes.attribute {
if attribute.name == "DESC" {
bom_desc = attribute.value.clone();
println!("Desc: {}", bom_desc);
}
if attribute.name == "PN" {
found = true;
bom_pn = attribute.value.clone();
println!("Part name: {}", bom_pn);
}
}
if bom_desc.is_empty() {
println!("Warning: Blank BOM description");
}
if !found {
println!("Please add PN attribute to schematic!");
std::process::exit(1);
}
let mut variant: Option<VariantDef> = None;
for v in &eagle.drawing.schematic.variantdefs.variantdef {
if v.current == Some("yes".to_string()) {
println!("Variant: {}", v.name);
variant = Some(v.clone());
break;
}
}
let variant = match variant {
Some(v) => v,
None => {
println!("Error: no active variant!");
std::process::exit(1);
}
};
let res = find_part_by_pn(&app.conn, &bom_pn);
println!();
match res {
Ok(bom) => {
let question = format!("BOM {} found! Would you like to update it?", bom_pn);
let yes = app.prompt.ask_yes_no_question(&question);
if yes {
let question =
format!("BOM {} found! Would you like to up-rev the design?", bom_pn);
let yes = app.prompt.ask_yes_no_question(&question);
if yes {
revision = bom.ver + 1;
diesel::update(parts)
.set(ver.eq(revision))
.filter(id.eq(bom.id))
.execute(&app.conn)
.expect("Unable to update BOM revision!");
} else {
delete_bom_list_by_id_and_ver(&app.conn, &bom.id, &bom.ver)
.expect("Unable to delete previous entries");
}
} else {
std::process::exit(0);
}
}
Err(_) => {
let part = models::NewUpdatePart {
pn: &bom_pn,
mpn: &bom_pn,
descr: &bom_desc,
ver: &revision,
mqty: &1,
};
create_part(&app.conn, &part).expect("Unable to create BOM part!");
}
}
println!("\nPARTS LIST:");
let list = get_line_items_from_parts(
&eagle.drawing.schematic.parts.part,
&variant,
&app.config.part_number_ignore_list,
);
let mut simple_part_list: Vec<SimplePart> = Vec::new();
for item in list {
let part = get_simplepart_from_library(&item, &eagle, &app.config.library_name);
if part.mpn.is_empty() {
println!("Manufacturer part number must be set for {}", part.pn);
std::process::exit(1);
}
simple_part_list.push(part.clone());
let existing = find_part_by_pn(&app.conn, &part.pn);
let npart = models::NewUpdatePart {
pn: &part.pn,
mpn: &part.mpn,
descr: &part.descr,
ver: &part.ver,
mqty: &part.mqty,
};
match existing {
Ok(e) => {
if prompt_to_update_part(&mut app.prompt, &npart, &e) {
update_part(&app.conn, &e.id, &npart).expect("Error updating part!");
}
}
Err(_) => {
println!("Creating: {:?}", npart);
create_part(&app.conn, &npart).expect("Unable to create part!");
}
}
let line_item = find_part_by_pn(&app.conn, &npart.pn);
let bom_item = find_part_by_pn(&app.conn, &bom_pn);
if let (Ok(li), Ok(bi)) = (line_item, bom_item) {
let relationship = models::NewPartsParts {
quantity: &item.quantity,
bom_ver: &bi.ver,
refdes: &item.name,
nostuff: &item.nostuff,
bom_part_id: &bi.id,
part_id: &li.id,
};
create_bom_line_item(&app.conn, &relationship)
.expect("Unable to add new BOM line item.");
}
}
print_simple_part_list(&simple_part_list);
}
pub fn show(app: &mut crate::Application, part_number: &str, version: &Option<i32>) {
use crate::schema::*;
let part = find_part_by_pn(&app.conn, &part_number);
if part.is_err() {
println!("{} was not found!", part_number);
std::process::exit(1);
}
let part = part.unwrap();
let mut table = Table::new();
let ver = match version {
Some(x) => x,
None => &part.ver,
};
let mut results = parts_parts::dsl::parts_parts
.filter(parts_parts::dsl::bom_part_id.eq(part.id))
.filter(parts_parts::dsl::bom_ver.eq(ver))
.load::<models::PartsPart>(&app.conn)
.expect("Error loading parts");
results.sort_by(|a, b| a.refdes.cmp(&b.refdes));
println!("Displaying {} parts", results.len());
println!(
"Part Number: {} BOM Id: {} Version: {}",
part.pn, part.id, ver
);
table.add_row(row![
"QUANTITY",
"REFDES",
"PN",
"MPN",
"DESC",
"VER",
"INVENTORY QTY",
"NO STUFF"
]);
for entry in results {
let details = find_part_by_id(&app.conn, &entry.part_id).expect("Unable to get details!");
let inventory = inventories::dsl::inventories
.filter(inventories::dsl::part_id.eq(entry.part_id))
.load::<models::Inventory>(&app.conn)
.expect("Error loading parts");
let mut inventory_qty = 0;
for item in inventory {
inventory_qty += item.quantity;
}
table.add_row(row![
entry.quantity,
entry.refdes,
details.pn,
details.mpn,
details.descr,
details.ver,
inventory_qty,
entry.nostuff,
]);
}
table.printstd();
}
pub fn export(app: &mut crate::Application, part_number: &str, version: &Option<i32>) {
use crate::schema::*;
let part = find_part_by_pn(&app.conn, &part_number);
if part.is_err() {
println!("{} was not found!", part_number);
std::process::exit(1);
}
let part = part.unwrap();
let ver = match version {
Some(x) => x,
None => &part.ver,
};
let mut results = parts_parts::dsl::parts_parts
.filter(parts_parts::dsl::bom_part_id.eq(part.id))
.filter(parts_parts::dsl::bom_ver.eq(ver))
.load::<models::PartsPart>(&app.conn)
.expect("Error loading parts");
results.sort_by(|a, b| {
let first = a.refdes.chars().next().unwrap();
let second = b.refdes.chars().next().unwrap();
first.cmp(&second)
});
let filename = format!("{}-v{}-{}.csv", part_number, ver, Utc::now().to_rfc3339());
let file = File::create(&filename).unwrap();
let file = BufWriter::new(file);
let mut wtr = csv::Writer::from_writer(file);
for entry in results {
let details = find_part_by_id(&app.conn, &entry.part_id).expect("Unable to get details!");
let inventory = inventories::dsl::inventories
.filter(inventories::dsl::part_id.eq(entry.part_id))
.load::<models::Inventory>(&app.conn)
.expect("Error loading parts");
let mut inventory_qty = 0;
for item in inventory {
inventory_qty += item.quantity;
}
let line = BomEntry {
quantity: entry.quantity,
refdes: entry.refdes,
pn: details.pn,
mpn: details.mpn,
descr: details.descr,
ver: details.ver,
inventory_qty,
no_stuff: entry.nostuff,
};
wtr.serialize(line).expect("Unable to serialize.");
wtr.flush().expect("Unable to flush");
}
println!("Inventory list exported to {}", filename);
}