diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/banking/account.rs | 77 | ||||
| -rw-r--r-- | src/banking/asset.rs | 57 | ||||
| -rw-r--r-- | src/banking/mod.rs | 61 | ||||
| -rw-r--r-- | src/main.rs | 7 | ||||
| -rw-r--r-- | src/parsers/ini/mod.rs | 28 | ||||
| -rw-r--r-- | src/parsers/mod.rs | 2 | ||||
| -rw-r--r-- | src/web_frontend/asset.rs | 61 | ||||
| -rw-r--r-- | src/web_frontend/balance.rs | 56 | ||||
| -rw-r--r-- | src/web_frontend/chart.rs | 79 | ||||
| -rw-r--r-- | src/web_frontend/mod.rs | 24 | ||||
| -rw-r--r-- | src/web_frontend/transactions.rs | 22 | ||||
| -rw-r--r-- | src/web_frontend/util.rs | 50 |
12 files changed, 352 insertions, 172 deletions
diff --git a/src/banking/account.rs b/src/banking/account.rs new file mode 100644 index 0000000..6994bb4 --- /dev/null +++ b/src/banking/account.rs @@ -0,0 +1,77 @@ +use parsers::csv::CsvFile; + +pub struct Account { + pub name : String, + pub iban : String, + pub transactions : Vec<Transaction>, + pub institute : String +} + +impl Account { + pub fn new(name : String, iban : String, transactions : Vec<String>, institute : String) -> Account { + let mut trans = Vec::new(); + if institute == "Sparkasse" { + // TODO als function/lambda übergeben die konvertierung.. + for trans_file in transactions { + let file = CsvFile::from_file(&trans_file, ";", true); + match file { + Ok(f) => { let mut tran = Transaction::from_sparkasse_csv_file(f); + trans.append(&mut tran); + }, + Err(e) => panic!("account: new: error reading csv transaction file: {}", e) + } + } + } + Account { name : name, iban : iban, transactions : trans, institute : institute } + } +} + +#[derive(Serialize)] +#[derive(Clone)] +pub struct Transaction { + pub sender_name : String, + pub amount : f32, + pub reference : String, + pub date : chrono::NaiveDate +} + +impl Transaction { + pub fn from_sparkasse_csv_file(file : CsvFile) -> Vec<Transaction> { + let mut ret = Vec::new(); + for line in file.iter() { + let mut sender_name_f = String::from(""); + let mut sender_iban_f = String::from(""); + let mut amount_f : f32 = 0.0; + let mut reference_f = String::from(""); + let mut date_f = chrono::NaiveDate::parse_from_str("01.01.2019", "%d.%m.%Y").unwrap(); + match line.get(&String::from("Kontonummer")) { + Some(value) => sender_iban_f = value.to_string(), + None => println!("missing sender") + } + match line.get(&String::from("Beguenstigter/Zahlungspflichtiger")) { + Some(value) => sender_name_f = value.to_string(), + None => println!("missing sender") + } + match line.get(&String::from("Verwendungszweck")) { + Some(value) => reference_f = value.to_string(), + None => println!("missing refernce") + } + match line.get(&String::from("Betrag")) { + Some(value) => amount_f = value.parse().unwrap(), + None => println!("missing amount") + } + match line.get(&String::from("Valutadatum")) { + Some(value) => { + date_f = chrono::NaiveDate::parse_from_str(value, "%d.%m.%y").unwrap();} , + None => println!("missing date") + } + ret.push(Transaction { + sender_name : sender_name_f, + amount : amount_f, + reference : reference_f, + date : date_f }); + + } + ret + } +} diff --git a/src/banking/asset.rs b/src/banking/asset.rs new file mode 100644 index 0000000..7b7287b --- /dev/null +++ b/src/banking/asset.rs @@ -0,0 +1,57 @@ +use crate::banking::account::Account; +use crate::parsers::ini::IniFile; + +pub struct Asset { + accounts : Vec<Account> +} + +impl Asset { + pub fn from_ini_file(file_name : &str) -> Asset { + // read in ini file + let t = crate::parsers::ini::IniFile::from_file(file_name); + let file; + match t { + Ok(f) => file = f, + Err(e) => panic!("asset: from_ini_file: could not read ini file: {}", e) + } + + let mut accounts = Vec::new(); + for (account_name, config) in file.sections { + let mut tmp; + match config.get("Institut") { + Some(i) => tmp = i, + None => panic!("asset: parse ini file: could not find \"Institute\" key for account {}", account_name) + } + let mut institute = String::from(""); + if let Some(i) = tmp.get(0) { + institute = i.to_string(); + } + let trans_files; + match config.get("TransactionFile") { + Some(i) => trans_files = i, + None => panic!("asset: ini file: no \"TransactionFile\" for account: {}", account_name) + } + match config.get("IBAN") { + Some(i) => tmp = i, + None => panic!("asset: no name for account {}", account_name) + } + let mut iban = String::from(""); + if let Some(i) = tmp.get(0) { + iban = i.to_string(); + } + accounts.push(Account::new(account_name, iban, trans_files.to_vec(), institute)); + + + } + Asset { accounts : accounts } + } + + pub fn get_account_by_name(self, name : &str) -> Option<Account> { + for account in self.accounts { + if account.name == name { + return Some(account); + } + } + None + } +} diff --git a/src/banking/mod.rs b/src/banking/mod.rs index 410bae1..e936286 100644 --- a/src/banking/mod.rs +++ b/src/banking/mod.rs @@ -1,59 +1,2 @@ - -use parsers::csv::CsvFile; - - -pub struct Account { - name : String, - iban : String, - transactions : Vec<Transaction> -} - - -#[derive(Serialize)] -pub struct Transaction { - pub sender_name : String, - pub amount : f32, - pub reference : String, - pub date : chrono::NaiveDate -} - -impl Transaction { - pub fn from_sparkasse_csv_file(file : CsvFile) -> Vec<Transaction> { - let mut ret = Vec::new(); - for line in file.iter() { - let mut sender_name_f = String::from(""); - let mut sender_iban_f = String::from(""); - let mut amount_f : f32 = 0.0; - let mut reference_f = String::from(""); - let mut date_f = chrono::NaiveDate::parse_from_str("01.01.2019", "%d.%m.%Y").unwrap(); - match line.get(&String::from("Kontonummer")) { - Some(value) => sender_iban_f = value.to_string(), - None => println!("missing sender") - } - match line.get(&String::from("Beguenstigter/Zahlungspflichtiger")) { - Some(value) => sender_name_f = value.to_string(), - None => println!("missing sender") - } - match line.get(&String::from("Verwendungszweck")) { - Some(value) => reference_f = value.to_string(), - None => println!("missing refernce") - } - match line.get(&String::from("Betrag")) { - Some(value) => amount_f = value.parse().unwrap(), - None => println!("missing amount") - } - match line.get(&String::from("Valutadatum")) { - Some(value) => { - date_f = chrono::NaiveDate::parse_from_str(value, "%d.%m.%y").unwrap();} , - None => println!("missing date") - } - ret.push(Transaction { - sender_name : sender_name_f, - amount : amount_f, - reference : reference_f, - date : date_f }); - - } - ret - } -} +pub mod asset; +pub mod account; diff --git a/src/main.rs b/src/main.rs index 1325742..eef92b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,10 +20,13 @@ fn main() { // e.g. wheres is the asset config ini file // TODO how pass config to the modules/handler ? // launch web frontend + let asset_ini = "data/asset.ini"; + banking::asset::Asset::from_ini_file(asset_ini); rocket::ignite() .attach(Template::fairing()) - .mount("/", routes![web_frontend::account_handler, web_frontend::transactions::transaction_handler, + .mount("/", routes![web_frontend::transactions::transaction_handler, web_frontend::transactions::transaction_handler_post, - web_frontend::balance::balance_handler, web_frontend::static_handler]) + web_frontend::balance::balance_handler, web_frontend::static_handler, + web_frontend::chart::chart_handler, web_frontend::asset::asset_handler]) .launch(); } diff --git a/src/parsers/ini/mod.rs b/src/parsers/ini/mod.rs index 9633231..5095344 100644 --- a/src/parsers/ini/mod.rs +++ b/src/parsers/ini/mod.rs @@ -4,17 +4,13 @@ use std::io::BufRead; use std::collections::HashMap; pub struct IniFile { - pub section : HashMap<String, Vec<IniSection>> -} - -pub struct IniSection { - pub section_name : String, - pub properties : HashMap<String, Vec<String>> + // section_name, key, list of values + pub sections : HashMap<String, HashMap<String, Vec<String>>> } impl IniFile { pub fn from_file(path : &str) -> Result<IniFile, io::Error> { - let mut file = HashMap::new(); + let mut file : HashMap<String, HashMap<String,Vec<String>>> = HashMap::new(); let fd = File::open(path)?; let reader = io::BufReader::new(fd); let mut current_section = String::from(""); @@ -38,7 +34,7 @@ impl IniFile { } current_section = n.to_string(); // TODO maybe the sections has been specified twice? - file.insert(n.to_string(), Vec::new() ); + file.insert(n.to_string(), HashMap::new() ); continue; } @@ -51,10 +47,22 @@ impl IniFile { if let Some(t) = kv.get(1) { value = t.to_string(); } + println!("key: {}, value: {}", key, value); if let Some(section) = file.get_mut(¤t_section) { - // get the entry with key from vector + println!("found current section"); + // get the values for key in section current_section + let mut hack_first = true; if let Some(ent) = section.get_mut(&key) { - ent.insert(value.to_string()); + println!("found key in map"); + ent.push(value.to_string()); + hack_first = false; + } + if hack_first { + let mut new = Vec::new(); + new.push(value.to_string()); + section.insert(key, new); + // TODO create new HashMap and insert + println!("inserted new one"); } } } diff --git a/src/parsers/mod.rs b/src/parsers/mod.rs index d8bce3c..5c59a19 100644 --- a/src/parsers/mod.rs +++ b/src/parsers/mod.rs @@ -1,2 +1,2 @@ pub mod csv; -//pub mod ini; +pub mod ini; diff --git a/src/web_frontend/asset.rs b/src/web_frontend/asset.rs new file mode 100644 index 0000000..f69f184 --- /dev/null +++ b/src/web_frontend/asset.rs @@ -0,0 +1,61 @@ +use std::collections::HashMap; +use rocket_contrib::templates::Template; +use crate::parsers::ini::IniFile; +use crate::parsers::csv::CsvFile; + + +#[derive(Serialize)] +pub struct AssetContext { + accounts : Vec<Account> +} + +#[derive(Serialize)] +pub struct Account { + name : String, + category : String, + balance : f32 +} + +#[get("/asset")] +pub fn asset_handler() -> rocket_contrib::templates::Template { + let asset_file = "data/asset.ini"; + let asset_config = IniFile::from_file(asset_file); + let ini_file; + match asset_config { + Ok(file) => ini_file = file, + Err(e) => panic!("could not read asset file") + } + + let mut acc = Vec::new(); + + for (account_name, config) in ini_file.sections { + let mut category = String::from(""); + if let Some(cat) = config.get("Category") { + if let Some(c) = cat.get(0) { + category = c.to_string(); + } + } + let mut all_trans = Vec::new(); + if let Some(transaction_files) = config.get("TransactionFile") { + for file in transaction_files { + let transactions = CsvFile::from_file(file, ";", true); + let mut t : Vec<crate::banking::account::Transaction> ; + match transactions { + Ok(trans) => t = crate::banking::account::Transaction::from_sparkasse_csv_file(trans), + Err(e) => panic!("could not read file {:?}", e) + } + all_trans.append(& mut t); + } + } + let balance = all_trans.iter().fold(0.0, |acc, x| acc + x.amount).abs(); + let tmp = Account { + name : account_name, + category : category.to_string(), + balance : balance + }; + acc.push(tmp); + } + + let context = AssetContext { accounts : acc }; + Template::render("asset", context) +} diff --git a/src/web_frontend/balance.rs b/src/web_frontend/balance.rs index 2cbebec..77311b0 100644 --- a/src/web_frontend/balance.rs +++ b/src/web_frontend/balance.rs @@ -1,5 +1,5 @@ use parsers::csv::CsvFile; -use banking::Account; +use banking::account::Account; //use parsers::ini::IniFile; use std::collections::HashMap; use rocket_contrib::templates::Template; @@ -10,6 +10,7 @@ use rocket::http::RawStr; use regex::Regex; use chrono::{NaiveDate, Utc}; use chrono::Datelike; +use crate::web_frontend::util; #[derive(Serialize)] struct MonthEarnSpend { @@ -27,53 +28,6 @@ struct BalanceContext { date_end : String } -#[derive(Debug)] -struct DateRange { - start_year : i32, - start_month : u32, - end_year : i32, - end_month : u32, -} - -impl DateRange { - fn new(start : chrono::NaiveDate, end : chrono::NaiveDate) -> DateRange { - DateRange { - start_year : start.year(), - start_month : start.month(), - end_year : end.year(), - end_month : end.month(), - } - } -} - -impl Iterator for DateRange { - type Item = chrono::NaiveDate; - - fn next(&mut self) -> Option<Self::Item> { - println!("next called"); - if (self.start_year <= self.end_year) { - if(self.start_month <= self.end_month) { - let mut tmp = self.start_year.to_string(); - if self.start_month < 10 { - tmp.push_str("-0"); - } else { - tmp.push_str("-"); - } - tmp.push_str(&self.start_month.to_string()); - tmp.push_str("-01"); - if self.start_month < 13 { - self.start_month = self.start_month + 1; - } else { - self.start_month = 1; - self.start_year = self.start_year + 1; - } - println!("{}", tmp); - return Some(chrono::NaiveDate::parse_from_str(&tmp, "%Y-%m-%d").unwrap()) - } - } - None - } -} #[get("/balance?<start>&<end>")] pub fn balance_handler(start : Option<&RawStr>, end : Option<&RawStr>) -> rocket_contrib::templates::Template { @@ -89,14 +43,14 @@ pub fn balance_handler(start : Option<&RawStr>, end : Option<&RawStr>) -> rocket chrono::NaiveDate::parse_from_str(&tmp, "%Y-%m-%d").unwrap() }, None => Utc::today().naive_utc() }; - let date_range = DateRange::new(date_start, date_end); + let date_range = crate::web_frontend::util::DateRange::new(date_start, date_end); let mut earn_spend_v = Vec::new(); for date in date_range { let transactions = CsvFile::from_file("data/t.csv", ";", true); - let t : Vec<crate::banking::Transaction> ; + let t : Vec<crate::banking::account::Transaction> ; match transactions { - Ok(trans) => t = crate::banking::Transaction::from_sparkasse_csv_file(trans), + Ok(trans) => t = crate::banking::account::Transaction::from_sparkasse_csv_file(trans), Err(e) => panic!("could not read file {:?}", e) } let result : Vec<_> = t.iter().filter(|x| x.date.month() == date.month()).collect(); diff --git a/src/web_frontend/chart.rs b/src/web_frontend/chart.rs index c5948fe..7065c6d 100644 --- a/src/web_frontend/chart.rs +++ b/src/web_frontend/chart.rs @@ -1,6 +1,7 @@ -use parsers::csv::CsvFile; -use banking::Account; -//use parsers::ini::IniFile; +use crate::parsers::csv::CsvFile; +use crate::banking::account::Account; +use crate::banking::asset::Asset; +use crate::parsers::ini::IniFile; use std::collections::HashMap; use rocket_contrib::templates::Template; use rocket::response::NamedFile; @@ -14,12 +15,29 @@ use chrono::Datelike; #[derive(Serialize)] struct ChartContext { account_name : String, - groups : HashMap<String, f32> + groups : HashMap<String, f32>, + total_sum : f32, + total_chart : f32, + date_start : String, + date_end : String } -#[get("/chart")] -fn chart_handler() -> rocket_contrib::templates::Template { +#[get("/chart?<start>&<end>")] +pub fn chart_handler(start : Option<&RawStr>, end : Option<&RawStr>) -> rocket_contrib::templates::Template { + let date_start = match start { + Some(s) => { let mut tmp = s.to_string(); + tmp.push_str("-01"); + chrono::NaiveDate::parse_from_str(&tmp, "%Y-%m-%d").unwrap() }, + None => Utc::today().naive_utc() + }; + let date_end = match end { + Some(s) => { let mut tmp = s.to_string(); + tmp.push_str("-01"); + chrono::NaiveDate::parse_from_str(&tmp, "%Y-%m-%d").unwrap() }, + None => Utc::today().naive_utc() + }; + let date_range = crate::web_frontend::util::DateRange::new(date_start, date_end); // read group config let chart_file = "data/giro"; let chart_config = IniFile::from_file(chart_file); @@ -29,33 +47,52 @@ fn chart_handler() -> rocket_contrib::templates::Template { Err(e) => panic!("could not read group file {:?}", e) } let mut groups = HashMap::new(); + + let asset_ini = "data/asset.ini"; + let asset = crate::banking::asset::Asset::from_ini_file(asset_ini); + let transactions = asset.get_account_by_name("Girokonto"); + let acc; + match transactions { + Some(trans) => acc = trans, + None => panic!("could not read file") + } + let t = acc.transactions; + // filter transaction to match only the specified timeframe + println!("unfiltered number: {}", t.len()); + let mut t_filtered = Vec::new(); + for date in date_range { + let mut tmp : Vec<_> = t.iter().filter(|x| x.date.month() == date.month()).collect(); + t_filtered.append(& mut tmp); + + } + println!("filtered number: {}", t_filtered.len()); + let total_sum = t_filtered.iter().filter(|t| t.amount < 0.0 ) + .fold(0.0, |acc, x| acc + x.amount).abs(); + println!("total sum: {}", total_sum); + let mut total_chart = 0.0; for (section_name, entries) in ini_file.sections { let mut complete = 0.0; - println!("section name: {}", section_name); - for entrie in entries { - for val in entrie.values { - if entrie.name.is_empty() || val.is_empty() { + for (key, values) in entries { + for val in values { + let mut t_filtered_cloned = t_filtered.clone(); + if val.is_empty() || val.is_empty() { continue } - println!("entrie is : {}", entrie.name); - let transactions = CsvFile::from_file("data/t.csv", ";", true); - let t : Vec<banking::Transaction> ; - match transactions { - Ok(trans) => t = banking::Transaction::from_sparkasse_csv_file(trans), - Err(e) => panic!("could not read file {:?}", e) - } - let re = Regex::new(&val).unwrap(); - let tmp = t.into_iter().filter(|transaction| + let re = Regex::new(&val).unwrap(); + let tmp = t_filtered_cloned.into_iter().filter(|transaction| re.is_match(&transaction.sender_name) ) .fold(0.0, |acc, x| acc + x.amount); - complete = complete + tmp.abs(); + complete = complete + tmp.abs(); } } groups.insert(section_name, complete); + total_chart = total_chart + complete; // ALSO INSERT OTHER, AKA THE REST } let context = ChartContext { account_name : String::from("Girokonto"), - groups : groups }; + groups : groups, total_sum : total_sum, total_chart : total_chart, + date_start : date_start.to_string()[0..7].to_string(), + date_end : date_end.to_string()[0..7].to_string() }; Template::render("chart", context) } diff --git a/src/web_frontend/mod.rs b/src/web_frontend/mod.rs index 8577065..3f58227 100644 --- a/src/web_frontend/mod.rs +++ b/src/web_frontend/mod.rs @@ -1,27 +1,13 @@ pub mod transactions; pub mod balance; -use parsers::csv::CsvFile; -use crate::banking::Account; -//use parsers::ini::IniFile; -use std::collections::HashMap; -use rocket_contrib::templates::Template; +pub mod chart; +pub mod asset; +mod util; use rocket::response::NamedFile; use std::path::{PathBuf, Path}; -use rocket::request::Form; -use rocket::http::RawStr; -use regex::Regex; -use chrono::{NaiveDate, Utc}; -use chrono::Datelike; -/* - * Overview over all accounts, complete asset overview? - */ -#[get("/")] -pub fn account_handler() -> rocket_contrib::templates::Template { - let context : HashMap<u32, u32> = HashMap::new(); - Template::render("account", context) -} -// allow always access + +// allow always access to static files #[get("/static/<file..>")] pub fn static_handler(file: PathBuf) -> Option<NamedFile> { NamedFile::open(Path::new("static/").join(file)).ok() diff --git a/src/web_frontend/transactions.rs b/src/web_frontend/transactions.rs index 417ca96..28e2dd1 100644 --- a/src/web_frontend/transactions.rs +++ b/src/web_frontend/transactions.rs @@ -1,5 +1,6 @@ use parsers::csv::CsvFile; -use crate::banking::Account; +use crate::banking::account::Account; +use crate::banking::asset::Asset; //use parsers::ini::IniFile; use std::collections::HashMap; use rocket_contrib::templates::Template; @@ -22,7 +23,7 @@ use chrono::Datelike; */ #[derive(Serialize)] struct TransactionContext { - transactions : Vec<crate::banking::Transaction>, + transactions : Vec<crate::banking::account::Transaction>, account_name : String, filter : String, date_start : String, @@ -35,13 +36,16 @@ pub struct TransactionFilter { } impl TransactionFilter { - pub fn apply_filter(&self, transactions : Vec<crate::banking::Transaction>) -> Vec<crate::banking::Transaction> { - let transactions = CsvFile::from_file("data/t.csv", ";", true); - let t : Vec<crate::banking::Transaction> ; + pub fn apply_filter(&self, transactions : Vec<crate::banking::account::Transaction>) -> Vec<crate::banking::account::Transaction> { + let asset_ini = "data/asset.ini"; + let asset : Asset = crate::banking::asset::Asset::from_ini_file(asset_ini); + let transactions = asset.get_account_by_name("Girokonto"); + let acc; match transactions { - Ok(trans) => t = crate::banking::Transaction::from_sparkasse_csv_file(trans), - Err(e) => panic!("could not read file {:?}", e) + Some(trans) => acc = trans, + None => panic!("could not read file") } + let t = acc.transactions; //self.filter.split("=").collect::<Vec<&str>>(); let re = Regex::new(&self.filter).unwrap(); let tmp = t.into_iter().filter(|transaction| re.is_match(&transaction.sender_name) || re.is_match(&transaction.reference) ).collect(); @@ -89,9 +93,9 @@ pub fn transaction_handler(start : Option<&RawStr>, end : Option<&RawStr>) -> ro None => Utc::today().naive_utc() }; let transactions = CsvFile::from_file("data/t.csv", ";", true); - let t : Vec<crate::banking::Transaction> ; + let t : Vec<crate::banking::account::Transaction> ; match transactions { - Ok(trans) => t = crate::banking::Transaction::from_sparkasse_csv_file(trans), + Ok(trans) => t = crate::banking::account::Transaction::from_sparkasse_csv_file(trans), Err(e) => panic!("could not read file {:?}", e) } let context = TransactionContext { transactions: t, account_name : String::from("Girokonto"), diff --git a/src/web_frontend/util.rs b/src/web_frontend/util.rs new file mode 100644 index 0000000..cffef0e --- /dev/null +++ b/src/web_frontend/util.rs @@ -0,0 +1,50 @@ +use chrono::{NaiveDate, Utc}; +use chrono::Datelike; + +#[derive(Debug)] +pub struct DateRange { + start_year : i32, + start_month : u32, + end_year : i32, + end_month : u32, +} + +impl DateRange { + pub fn new(start : chrono::NaiveDate, end : chrono::NaiveDate) -> DateRange { + DateRange { + start_year : start.year(), + start_month : start.month(), + end_year : end.year(), + end_month : end.month(), + } + } +} + +impl Iterator for DateRange { + type Item = chrono::NaiveDate; + + fn next(&mut self) -> Option<Self::Item> { + println!("next called"); + if (self.start_year <= self.end_year) { + if(self.start_month <= self.end_month) { + let mut tmp = self.start_year.to_string(); + if self.start_month < 10 { + tmp.push_str("-0"); + } else { + tmp.push_str("-"); + } + tmp.push_str(&self.start_month.to_string()); + tmp.push_str("-01"); + if self.start_month < 13 { + self.start_month = self.start_month + 1; + } else { + self.start_month = 1; + self.start_year = self.start_year + 1; + } + println!("{}", tmp); + return Some(chrono::NaiveDate::parse_from_str(&tmp, "%Y-%m-%d").unwrap()) + } + } + None + } +} |
