Initial commit
This commit is contained in:
commit
95516d9405
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
.~*
|
||||||
|
*.ods
|
1489
Cargo.lock
generated
Normal file
1489
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "bom-combine"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
spreadsheet-ods = "0.12"
|
||||||
|
csv = "1.2"
|
||||||
|
clap = { version = "4.3", features = ["derive"] }
|
||||||
|
color-eyre = "0.6"
|
||||||
|
log = "0.4"
|
||||||
|
stderrlog = "0.5"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
icu_locid = "1.2"
|
179
src/main.rs
Normal file
179
src/main.rs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
collections::{hash_map::Entry, HashMap},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use icu_locid::locale;
|
||||||
|
use log::{debug, error, info};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use spreadsheet_ods::{
|
||||||
|
formula::{fcellref, fcellrefa, fcellrefa_table},
|
||||||
|
write_ods, CellStyle, CellStyleRef, Sheet, ValueFormatNumber, WorkBook,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
struct Args {
|
||||||
|
#[arg(required = true)]
|
||||||
|
input: Vec<String>,
|
||||||
|
output: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
struct Record {
|
||||||
|
#[serde(rename(deserialize = "Reference"))]
|
||||||
|
reference: String,
|
||||||
|
#[serde(rename(deserialize = "Qty"))]
|
||||||
|
qty: u64,
|
||||||
|
#[serde(rename(deserialize = "Value"))]
|
||||||
|
value: String,
|
||||||
|
#[serde(rename(deserialize = "Footprint"))]
|
||||||
|
footprint: String,
|
||||||
|
#[serde(rename(deserialize = "WE Part"))]
|
||||||
|
we_part: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Bom {
|
||||||
|
name: String,
|
||||||
|
entries: Vec<Record>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
color_eyre::install()?;
|
||||||
|
stderrlog::new()
|
||||||
|
.module(module_path!())
|
||||||
|
.verbosity(3)
|
||||||
|
.init()?;
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let mut boms = vec![];
|
||||||
|
|
||||||
|
for path in args.input {
|
||||||
|
let path = Path::new(&path);
|
||||||
|
if !path.exists() {
|
||||||
|
error!("{:?} does not exist", path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
info!("Opening {:?} as input", path);
|
||||||
|
let mut reader = csv::Reader::from_path(path)?;
|
||||||
|
let bom = Bom {
|
||||||
|
name: path.file_name().unwrap().to_str().unwrap().to_owned(),
|
||||||
|
entries: reader.deserialize().collect::<Result<Vec<_>, _>>()?,
|
||||||
|
};
|
||||||
|
boms.push(bom);
|
||||||
|
}
|
||||||
|
let mut wb = WorkBook::new(locale!("en_US"));
|
||||||
|
|
||||||
|
let mut summary_sheet = Sheet::new("Summary");
|
||||||
|
summary_sheet.set_value(0, 0, "#");
|
||||||
|
summary_sheet.set_value(0, 1, "Qty");
|
||||||
|
summary_sheet.set_value(0, 2, "Value");
|
||||||
|
summary_sheet.set_value(0, 3, "Footprint");
|
||||||
|
summary_sheet.set_value(0, 4, "WE Part");
|
||||||
|
summary_sheet.set_value(0, 5, "Price per Unit");
|
||||||
|
summary_sheet.set_value(0, 6, "Total Price");
|
||||||
|
summary_sheet.set_value(0, 7, "Link");
|
||||||
|
summary_sheet.set_value(0, 10, "Name");
|
||||||
|
summary_sheet.set_value(0, 11, "Amount");
|
||||||
|
wb.push_sheet(summary_sheet);
|
||||||
|
|
||||||
|
let mut parts: Vec<(_, _, Option<_>, _)> = vec![];
|
||||||
|
|
||||||
|
for bom in boms {
|
||||||
|
info!("Coppying {} to output file", bom.name);
|
||||||
|
let mut sheet = Sheet::new(&bom.name);
|
||||||
|
sheet.set_value(0, 0, "#");
|
||||||
|
sheet.set_value(0, 1, "Reference");
|
||||||
|
sheet.set_value(0, 2, "Qty");
|
||||||
|
sheet.set_value(0, 3, "Value");
|
||||||
|
sheet.set_value(0, 4, "Footprint");
|
||||||
|
sheet.set_value(0, 5, "WE Part");
|
||||||
|
|
||||||
|
for (i, entry) in bom.entries.iter().enumerate() {
|
||||||
|
let row = u32::try_from(i)? + 1;
|
||||||
|
sheet.set_value(row, 0, row);
|
||||||
|
sheet.set_value(row, 1, &entry.reference);
|
||||||
|
sheet.set_value(row, 2, entry.qty);
|
||||||
|
sheet.set_value(row, 3, &entry.value);
|
||||||
|
sheet.set_value(row, 4, &entry.footprint);
|
||||||
|
if let Some(we_part) = entry.we_part.to_owned() {
|
||||||
|
sheet.set_value(row, 5, we_part);
|
||||||
|
}
|
||||||
|
let Some((_, _, _, refs)) = parts.iter_mut().find(|(value, footprint, we_part, _)| {
|
||||||
|
value == &entry.value
|
||||||
|
&& footprint == &entry.footprint
|
||||||
|
&& (we_part.is_none() || entry.we_part.is_none() || *we_part == entry.we_part)
|
||||||
|
}) else {
|
||||||
|
parts.push((entry.value.to_owned(), entry.footprint.to_owned(), entry.we_part.to_owned(), vec![(bom.name.to_owned(), row)]));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
refs.push((bom.name.to_owned(), row));
|
||||||
|
}
|
||||||
|
|
||||||
|
wb.push_sheet(sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Sorting parts");
|
||||||
|
parts.sort_unstable_by(
|
||||||
|
|(value_a, footprint_a, _, _), (value_b, footprint_b, _, _)| match footprint_a
|
||||||
|
.cmp(footprint_b)
|
||||||
|
{
|
||||||
|
Ordering::Less => Ordering::Less,
|
||||||
|
Ordering::Equal => value_a.cmp(value_b),
|
||||||
|
Ordering::Greater => Ordering::Greater,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("Creating summary sheet");
|
||||||
|
let mut boards = vec![];
|
||||||
|
let summary_sheet = wb.sheet_mut(0);
|
||||||
|
for (i, (value, footprint, we_part, refs)) in parts.iter().enumerate() {
|
||||||
|
let row = u32::try_from(i)? + 1;
|
||||||
|
summary_sheet.set_value(row, 0, row);
|
||||||
|
summary_sheet.set_value(row, 2, value);
|
||||||
|
summary_sheet.set_value(row, 3, footprint);
|
||||||
|
if let Some(we_part) = we_part {
|
||||||
|
summary_sheet.set_value(row, 4, we_part);
|
||||||
|
}
|
||||||
|
summary_sheet.set_formula(row, 6, format!("{}*{}", fcellref(row, 5), fcellref(row, 1)));
|
||||||
|
|
||||||
|
let mut qty_formula = "".to_owned();
|
||||||
|
for (board, row) in refs {
|
||||||
|
let board_index = boards
|
||||||
|
.iter()
|
||||||
|
.find_map(|(b, i)| if *b == board { Some(*i) } else { None })
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let index = u32::try_from(boards.len()).unwrap() + 1;
|
||||||
|
boards.push((board, index));
|
||||||
|
summary_sheet.set_value(index, 10, board);
|
||||||
|
summary_sheet.set_value(index, 11, 1);
|
||||||
|
index
|
||||||
|
});
|
||||||
|
if qty_formula == "" {
|
||||||
|
qty_formula = format!(
|
||||||
|
"{}*{}",
|
||||||
|
fcellrefa_table(board, *row, 2),
|
||||||
|
fcellrefa(board_index, 11)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qty_formula.push_str(
|
||||||
|
format!(
|
||||||
|
"+{}*{}",
|
||||||
|
fcellrefa_table(board, *row, 2),
|
||||||
|
fcellrefa(board_index, 11)
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
summary_sheet.set_formula(row, 1, qty_formula);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Saving to {:?}", args.output);
|
||||||
|
write_ods(&mut wb, args.output)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user