inventory_mgt/lib.rs
1//! # Inventory Management
2//!
3//! This crate is a port of an existing python project. It allows one to sync the current
4//! quantities from a supply inventory csv file to a filtered down view they have created of their
5//! own truncated "master inventory" (their own inventory)
6//!
7//! ### This program will update the quantity in master inventory with the quantity found in supply inventory based for each part in master inventory
8//!
9//!
10//! - The default filenames that program accepts are:
11//! - "SupplyInventory.csv" for the _supply list_
12//! - "MasterInventory.csv" for the _master list_
13//! - _You can set your own filenames but you must specify their name and location in command line arguments_
14//! - _See how to use command line arguments below_
15//!
16//!
17//! - Both csv files must include the following columns:
18//!
19//! | VenCode | PartNumber | TotalQty |
20//! | ------- |:----------:|:--------:|
21//!
22//! ## Installation
23//!
24//! You can install this application in one of two ways. Either clone the repo and build the
25//! release version with cargo, or simply use cargo install:
26//!
27//! ```terminal
28//! $ cargo install inventory-mgt
29//! ```
30//!
31//! ## Use
32//!
33//! To use this application, simply run it with the generate command to generate a new master csv
34//! with updated quantity fields:
35//!
36//! ```terminal
37//! $ inventory_mgt generate
38//! ```
39//!
40//! Make sure you are in the root folder where your `SupplyInventory.csv` and `MasterInventory.csv`
41//! files are. You can grab sample ones from the github repo.
42//!
43//! Optionally you can pass in custom filenames with flags after the `generate` command:
44//!
45//! ```terminal
46//! $ inventory_mgt generate -m masterinv.csv -s supplyinv.csv
47//! ```
48//!
49//! That's it! Enjoy!
50
51#[macro_use]
52extern crate structopt;
53
54#[macro_use]
55extern crate serde_derive;
56
57use csv;
58
59use structopt::StructOpt;
60use std::error::Error;
61use std::str;
62
63use indexmap::map::IndexMap;
64
65/**
66 * Command Line Parsing
67 */
68
69#[derive(StructOpt, Debug)]
70#[structopt(
71 name = "inventorymgt",
72 about = "Updates Master Inventory from Supply List CSV.",
73 long_about = "You can use this terminal program to populate a new csv, products from master inventory and quantitties from the newest supply inventory csv."
74)]
75/// InventoryConfig provides a structure for structopt to take in commands.
76pub enum InventoryConfig {
77 #[structopt(name = "generate")]
78 /// Generates a new csv file with updated quantities from the supply csv.
79 Generate {
80 /// Specifies the filename for master inventory csv and defaults to MasterInventory.csv.
81 #[structopt(short = "m", long = "master", default_value = "MasterInventory.csv")]
82 master_filename: String,
83
84 /// specifies the filename for supply inventory csv and defaults to SupplyInventory.csv
85 #[structopt(short = "s", long = "supply", default_value = "SupplyInventory.csv")]
86 supply_filename: String,
87 },
88}
89
90/**
91 * CSV Structs
92 */
93
94/// Provides the structures for a part from the Master csv list
95#[derive(Debug, PartialEq, Serialize, Deserialize)]
96#[serde(rename_all = "PascalCase")]
97pub struct MasterPart {
98 pub ven_code: String,
99 pub part_number: String,
100 #[serde(rename = "SKU")]
101 pub sku: String,
102 #[serde(deserialize_with = "csv::invalid_option")]
103 pub total_qty: Option<i32>,
104}
105
106impl MasterPart {
107
108 /// Updates total_qty with a supplied quantity, mutating the instance in place
109 pub fn update_qty(&mut self, qty: i32) {
110 self.total_qty = Some(qty);
111 }
112}
113
114#[derive(Debug, PartialEq, Deserialize)]
115#[serde(rename_all = "PascalCase")]
116struct SupplyPart<'a> {
117 ven_code: &'a str,
118 part_number: &'a [u8],
119 total_qty: i32,
120}
121
122/**
123 * MasterCache & Methods
124 */
125
126/// Holds the master inventory in a hashmap, where the key is the ven_code, and the value is a
127/// MasterPart struct. This is for efficiency when searching through the master cache.
128pub struct MasterCache {
129 pub products: IndexMap<String, Vec<MasterPart>>
130}
131
132impl MasterCache {
133
134 /// Takes in a filename for a MasterInventory csv file, and on success, returns
135 /// a MasterCache struct instance.
136 pub fn from(filename: &str) -> Result<MasterCache, Box<dyn Error>> {
137 let mut rdr = csv::Reader::from_path(filename)?;
138 let mut products: IndexMap<String, Vec<MasterPart>> = IndexMap::new();
139
140 for result in rdr.deserialize() {
141 let product: MasterPart = result?;
142 let ven_code = product.ven_code.clone();
143
144 if let Some(v_code) = products.get_mut(&ven_code) {
145 v_code.push(product);
146 } else {
147 products.insert(ven_code, vec![product]);
148 }
149 }
150
151 Ok(MasterCache {
152 products,
153 })
154 }
155
156 /// Writes a new csv file from a MasterCache instance
157 pub fn write_csv(&self, filename: &'static str) -> Result<(), Box<dyn Error>> {
158 let mut wtr = csv::Writer::from_path(filename)?;
159
160 for products in self.products.values() {
161 for product in products {
162 wtr.serialize(product)?;
163 }
164 }
165
166 wtr.flush()?;
167 Ok(())
168 }
169}
170
171/// Creates a new master csv file called "newmaster.csv" with the updated quantities, pulled
172/// from the supply csv file
173pub fn update_master(master_filename: String, supply_filename: String) -> Result<(), Box<dyn Error>> {
174 let mut master_cache = MasterCache::from(&master_filename)?;
175
176 let mut rdr = csv::Reader::from_path(supply_filename)?;
177 let mut raw_record = csv::ByteRecord::new();
178 let headers = rdr.byte_headers()?.clone();
179
180 while rdr.read_byte_record(&mut raw_record)? {
181 let product: SupplyPart = raw_record.deserialize(Some(&headers))?;
182 let ven_code = product.ven_code;
183 let product_qty = product.total_qty;
184
185 if let Some(v_code) = master_cache.products.get_mut(ven_code) {
186
187 // this ven_code is in our master_cache so let's see
188 // if the product is there and update it's quantity
189 if let Some(master_product) = v_code.iter_mut().find(|p| p.part_number == str::from_utf8(product.part_number).unwrap()) {
190 master_product.update_qty(product_qty);
191 }
192 }
193 }
194
195
196 // lastly let's write the updated master supply list
197 master_cache.write_csv("newmaster.csv")?;
198
199 Ok(())
200}
201
202/// `run` will take in an InventoryConfig enum config (parsed in `main`) and execute the appropriate
203/// program logic
204pub fn run(config: InventoryConfig) -> Result<(), Box<dyn Error>> {
205 match config {
206 InventoryConfig::Generate { master_filename, supply_filename } => update_master(master_filename, supply_filename)?,
207 }
208
209 Ok(())
210}