This commit is contained in:
2021-12-16 11:56:03 -06:00
parent df36291b7e
commit 424e0865c2
7 changed files with 333 additions and 124 deletions

72
Cargo.lock generated
View File

@@ -19,6 +19,26 @@ dependencies = [
"version_check", "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]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.0.1"
@@ -77,6 +97,21 @@ dependencies = [
"winapi", "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]] [[package]]
name = "codepage" name = "codepage"
version = "0.1.1" version = "0.1.1"
@@ -157,6 +192,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.109" version = "0.2.109"
@@ -285,6 +329,12 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.82" version = "1.0.82"
@@ -296,6 +346,15 @@ dependencies = [
"unicode-xid", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.30" version = "1.0.30"
@@ -327,6 +386,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
@@ -339,6 +404,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.3" version = "0.9.3"
@@ -379,6 +450,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"calamine", "calamine",
"chrono", "chrono",
"clap",
"rusqlite", "rusqlite",
"smallvec", "smallvec",
] ]

View File

@@ -8,6 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
calamine = "0.18.0" calamine = "0.18.0"
chrono = "0.4.19" chrono = "0.4.19"
clap = { version = "2.34.0"}
rusqlite = { version = "0.26.3", features = ["bundled"] } rusqlite = { version = "0.26.3", features = ["bundled"] }
smallvec = "1.7.0" smallvec = "1.7.0"

BIN
data.db

Binary file not shown.

64
sql/get_top.sql Normal file
View File

@@ -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;

137
src/converter/mod.rs Normal file
View File

@@ -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<dyn Error>> {
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<calamine::Range<DataType>, Box<dyn Error>> {
let mut excel: Xlsx<_> = open_workbook(path)?;
let r = excel
.worksheet_range("Export")
.expect("Is There")
?;
Ok(r)
}
fn get_index(r: &calamine::Range<DataType>) -> (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<DataType>, indexs: HashMap<&str,usize>, skip_count: usize) -> Result<Vec<Data>,Box<dyn Error>> {
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::<i64>()?;
let month = datestring[5..].parse::<i64>()?;
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<Data>) -> Result<(), Box<dyn Error>> {
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(())
}

View File

@@ -1,135 +1,58 @@
use std::{collections::HashMap, error::Error}; use std::error::Error;
use calamine::{open_workbook, Reader, Xlsx}; mod converter;
use rusqlite::{Connection, Result}; mod types;
const SCHEMA: &str = r#"CREATE TABLE IF NOT EXISTS data ( use clap::{App, Arg, SubCommand};
description text,
sku text,
level2 text,
level3 text,
revenue float,
cost float,
quantity integer,
year integer,
month integer
);
"#;
#[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<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let now = chrono::offset::Local::now(); let mut app = App::new("xlr")
let mut data_vec = Vec::with_capacity(100000); .arg(
let mut excel: Xlsx<_> = open_workbook("data.xlsx").unwrap(); Arg::with_name("convert")
let r = excel .short("c")
.worksheet_range("Export") .long("convert")
.expect("Is There") .value_name("FILE")
.expect("Excel Read Error"); .help("File to convert")
.takes_value(true),
let mut indexs = HashMap::new(); )
let mut skip_count = 0; .arg(
'outer: for row in r.rows() { Arg::with_name("get-top")
skip_count += 1; .short("gt")
for (i, elm) in row.iter().enumerate() { .long("get-top")
let header = elm.get_string().expect("header issue"); .value_names(&["YEAR", "MONTH"])
match header { .help("Year and month to get top info")
"FiscalYearMonth" => indexs.insert("date", i), .takes_value(true),
"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::<i64>().unwrap();
let month = datestring[5..].parse::<i64>().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
); );
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(); if !did_run {
let diff = after - now; helpper.print_help().unwrap();
println!("{:?}", diff.num_milliseconds()); }
Ok(()) Ok(())
} }

12
src/types/mod.rs Normal file
View File

@@ -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,
}