From 424e0865c2bd8bf20b996ba2076439e8e6873b8c Mon Sep 17 00:00:00 2001 From: robviren Date: Thu, 16 Dec 2021 11:56:03 -0600 Subject: [PATCH] CLI --- Cargo.lock | 72 ++++++++++++++++++ Cargo.toml | 1 + data.db | Bin 6520832 -> 6471680 bytes sql/get_top.sql | 64 ++++++++++++++++ src/converter/mod.rs | 137 ++++++++++++++++++++++++++++++++++ src/main.rs | 171 ++++++++++++------------------------------- src/types/mod.rs | 12 +++ 7 files changed, 333 insertions(+), 124 deletions(-) create mode 100644 sql/get_top.sql create mode 100644 src/converter/mod.rs create mode 100644 src/types/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 9dc9389..428cdb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,26 @@ dependencies = [ "version_check", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -77,6 +97,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "codepage" version = "0.1.1" @@ -157,6 +192,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "libc" version = "0.2.109" @@ -285,6 +329,12 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "syn" version = "1.0.82" @@ -296,6 +346,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.30" @@ -327,6 +386,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + [[package]] name = "unicode-xid" version = "0.2.2" @@ -339,6 +404,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.3" @@ -379,6 +450,7 @@ version = "0.1.0" dependencies = [ "calamine", "chrono", + "clap", "rusqlite", "smallvec", ] diff --git a/Cargo.toml b/Cargo.toml index 3463701..5691fcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] calamine = "0.18.0" chrono = "0.4.19" +clap = { version = "2.34.0"} rusqlite = { version = "0.26.3", features = ["bundled"] } smallvec = "1.7.0" diff --git a/data.db b/data.db index 13d52bdcb9ab288043e6da773fe5e84f6978d401..57cb995b77726b353d9f919e940fcf178b4fdb8e 100644 GIT binary patch delta 20229 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 Y>AKogFm`ue5YNFPxZ@9XO_}k(09*`_KmY&$ delta 40214 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 KD%;lND*HdQApnQ~ diff --git a/sql/get_top.sql b/sql/get_top.sql new file mode 100644 index 0000000..df40a98 --- /dev/null +++ b/sql/get_top.sql @@ -0,0 +1,64 @@ + SELECT + description, + sku, + level2, + level3, + revenue, + quantity, + trend + from + ( + SELECT + *, + ROW_NUMBER () OVER ( + PARTITION BY level2 + ORDER BY + SUM(revenue) DESC + ) rownum, + SUM(revenue) OVER (PARTITION BY level2) total, + ROUND(SUM(revenue), 2) as revenue, + SUM(quantity) as quantity, + trend + FROM + ( + select + *, + cast(past as float) / 3 as past, + round( + cast(present as float) /(cast(past as float) / 3.0), + 2 + ) as trend + from + ( + select + *, + sum(quantity) FILTER ( + WHERE + year = $year + and month between $($month - 4) + and $($month - 1) + ) over (PARTITION BY description) as past, + sum(quantity) FILTER ( + WHERE + year = $year + and month = %[2]s + ) over (PARTITION BY description) as present + from + data + ) + ) + where + year = %[1]s + and month = %[2]s + GROUP BY + description + ORDER BY + total DESC + ) + WHERE + rownum <= 10 + and revenue > 0 + order by + (sum(revenue) over ( + partition by level2 + )) desc; diff --git a/src/converter/mod.rs b/src/converter/mod.rs new file mode 100644 index 0000000..0044c93 --- /dev/null +++ b/src/converter/mod.rs @@ -0,0 +1,137 @@ +use std::{error::Error}; +use std::{collections::HashMap}; + +use rusqlite::{Connection, Result}; +use calamine::{open_workbook, Reader, Xlsx,DataType}; + +use super::types::Data; + +const SCHEMA: &str = r#"CREATE TABLE IF NOT EXISTS data ( + description text, + sku text, + level2 text, + level3 text, + revenue float, + cost float, + quantity integer, + year integer, + month integer +); +"#; + +pub fn convert_excel(path: &str) -> Result<(),Box> { + let r = get_excel(path)?; + let (indexs,skip_count) = get_index(&r); + let data_vec = parse_data(&r,indexs,skip_count)?; + write_sql(&data_vec)?; + Ok(()) +} + +fn get_excel(path: &str) -> Result, Box> { + let mut excel: Xlsx<_> = open_workbook(path)?; + let r = excel + .worksheet_range("Export") + .expect("Is There") + ?; + Ok(r) +} + +fn get_index(r: &calamine::Range) -> (HashMap<&'static str, usize>,usize) { + let mut indexs = HashMap::new(); + let mut skip_count = 0; + 'outer: for row in r.rows() { + skip_count += 1; + for (i, elm) in row.iter().enumerate() { + let header = elm.get_string().expect("header issue"); + match header { + "FiscalYearMonth" => indexs.insert("date", i), + "Level2" => indexs.insert("level2", i), + "Level3" => indexs.insert("level3", i), + "MaterialEntered" => indexs.insert("sku", i), + "Quantity" => indexs.insert("quantity", i), + "SalesRevenue" => indexs.insert("revenue", i), + "CostOfGoodsSold" => indexs.insert("cost", i), + "ProductDescription" => indexs.insert("description", i), + _ => None, + }; + if indexs.len() == 8 { + break 'outer; + } + } + } + (indexs,skip_count) +} + +fn parse_data(r: &calamine::Range, indexs: HashMap<&str,usize>, skip_count: usize) -> Result,Box> { + let mut data_vec = Vec::with_capacity(100000); + for row in r.rows().skip(skip_count) { + if row[indexs["sku"]].get_string() == None { + break; + } + let datestring = row[indexs["date"]].get_string().expect("Date Parsing Issue"); + let year = datestring[0..4].parse::()?; + let month = datestring[5..].parse::()?; + data_vec.push(Data { + description: String::from(row[indexs["description"]].get_string().unwrap().replace("\"", "")), + sku: String::from(row[indexs["sku"]].get_string().unwrap()), + level2: String::from(row[indexs["level2"]].get_string().unwrap()), + level3: String::from(row[indexs["level3"]].get_string().unwrap()), + revenue: match row[indexs["revenue"]].get_float() { + Some(x) => x, + None => match row[indexs["revenue"]].get_int() { + Some(x) => x as f64, + None => 0.0, + }, + }, + cost: match row[indexs["cost"]].get_float() { + Some(x) => x, + None => match row[indexs["cost"]].get_int() { + Some(x) => x as f64, + None => 0.0, + }, + }, + quantity: match row[indexs["quantity"]].get_int() { + Some(x) => x, + None => match row[indexs["quantity"]].get_float() { + Some(x) => x as i64, + None => 0, + }, + }, + year: year, + month: month, + }); + } + Ok(data_vec) +} + +fn write_sql(data_vec: &Vec) -> Result<(), Box> { + let conn = Connection::open("data.db")?; + conn.execute("DROP TABLE IF EXISTS data;", [])?; + conn.execute(SCHEMA, [])?; + + let mut inserts = Vec::with_capacity(100000); + + inserts.push(String::from("INSERT INTO data (description,sku,level2,level3,revenue,cost,quantity,year,month) VALUES ")); + + for row in data_vec { + let other: String = format!( + "(\"{}\",\"{}\",\"{}\",\"{}\",{},{},{},{},{}),", + row.description, + row.sku, + row.level2, + row.level3, + row.revenue, + row.cost, + row.quantity, + row.year, + row.month + ); + inserts.push(other); + } + let mut ins = inserts.join(""); + ins.pop(); + ins.push(';'); + + conn.execute(&ins, [])?; + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2c3321a..158ed9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,135 +1,58 @@ -use std::{collections::HashMap, error::Error}; +use std::error::Error; -use calamine::{open_workbook, Reader, Xlsx}; -use rusqlite::{Connection, Result}; +mod converter; +mod types; -const SCHEMA: &str = r#"CREATE TABLE IF NOT EXISTS data ( - description text, - sku text, - level2 text, - level3 text, - revenue float, - cost float, - quantity integer, - year integer, - month integer -); -"#; +use clap::{App, Arg, SubCommand}; -#[derive(Debug)] -struct Data { - description: String, - sku: String, - level2: String, - level3: String, - revenue: f64, - cost: f64, - quantity: i64, - year: i64, - month: i64, -} fn main() -> Result<(), Box> { - let now = chrono::offset::Local::now(); - let mut data_vec = Vec::with_capacity(100000); - let mut excel: Xlsx<_> = open_workbook("data.xlsx").unwrap(); - let r = excel - .worksheet_range("Export") - .expect("Is There") - .expect("Excel Read Error"); - - let mut indexs = HashMap::new(); - let mut skip_count = 0; - 'outer: for row in r.rows() { - skip_count += 1; - for (i, elm) in row.iter().enumerate() { - let header = elm.get_string().expect("header issue"); - match header { - "FiscalYearMonth" => indexs.insert("date", i), - "Level2" => indexs.insert("level2", i), - "Level3" => indexs.insert("level3", i), - "MaterialEntered" => indexs.insert("sku", i), - "Quantity" => indexs.insert("quantity", i), - "SalesRevenue" => indexs.insert("revenue", i), - "CostOfGoodsSold" => indexs.insert("cost", i), - "ProductDescription" => indexs.insert("description", i), - _ => None, - }; - if indexs.len() == 8 { - break 'outer; - } - } - } - - for row in r.rows().skip(skip_count) { - if row[indexs["sku"]].get_string() == None { - break; - } - let datestring = row[indexs["date"]].get_string().expect("Date Parsing Issue"); - let year = datestring[0..4].parse::().unwrap(); - let month = datestring[5..].parse::().unwrap(); - data_vec.push(Data { - description: String::from(row[indexs["description"]].get_string().unwrap().replace("\"", "")), - sku: String::from(row[indexs["sku"]].get_string().unwrap()), - level2: String::from(row[indexs["level2"]].get_string().unwrap()), - level3: String::from(row[indexs["level3"]].get_string().unwrap()), - revenue: match row[indexs["revenue"]].get_float() { - Some(x) => x, - None => match row[indexs["revenue"]].get_int() { - Some(x) => x as f64, - None => 0.0, - }, - }, - cost: match row[indexs["cost"]].get_float() { - Some(x) => x, - None => match row[indexs["cost"]].get_int() { - Some(x) => x as f64, - None => 0.0, - }, - }, - quantity: match row[indexs["quantity"]].get_int() { - Some(x) => x, - None => match row[indexs["quantity"]].get_float() { - Some(x) => x as i64, - None => 0, - }, - }, - year: year, - month: month, - }); - } - - let conn = Connection::open("data.db")?; - conn.execute("DROP TABLE IF EXISTS data;", [])?; - conn.execute(SCHEMA, [])?; - - let mut inserts = Vec::with_capacity(100000); - - inserts.push(String::from("INSERT INTO data (description,sku,level2,level3,revenue,cost,quantity,year,month) VALUES ")); - - for row in data_vec { - let other: String = format!( - "(\"{}\",\"{}\",\"{}\",\"{}\",{},{},{},{},{}),", - row.description, - row.sku, - row.level2, - row.level3, - row.revenue, - row.cost, - row.quantity, - row.year, - row.month + let mut app = App::new("xlr") + .arg( + Arg::with_name("convert") + .short("c") + .long("convert") + .value_name("FILE") + .help("File to convert") + .takes_value(true), + ) + .arg( + Arg::with_name("get-top") + .short("gt") + .long("get-top") + .value_names(&["YEAR", "MONTH"]) + .help("Year and month to get top info") + .takes_value(true), ); - inserts.push(other); + + let mut helpper = app.clone(); + let matches = app.get_matches(); + let mut did_run = false; + + //Convert + match matches.value_of("convert") { + Some(val) => { + let now = chrono::offset::Local::now(); + converter::convert_excel(val)?; + println!( + "{:?}", + (chrono::offset::Local::now() - now).num_milliseconds() + ); + did_run = true; + }, + None => {}, } - let mut ins = inserts.join(""); - ins.pop(); - ins.push(';'); - conn.execute(&ins, [])?; + //Get Top + match matches.values_of("get-top") { + Some(vals) => { + did_run = true; + }, + None => {} + } - let after = chrono::offset::Local::now(); - let diff = after - now; - println!("{:?}", diff.num_milliseconds()); + if !did_run { + helpper.print_help().unwrap(); + } Ok(()) } diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..d934b1c --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,12 @@ +#[derive(Debug)] +pub struct Data { + pub description: String, + pub sku: String, + pub level2: String, + pub level3: String, + pub revenue: f64, + pub cost: f64, + pub quantity: i64, + pub year: i64, + pub month: i64, +} \ No newline at end of file