diff --git a/Cargo.lock b/Cargo.lock index e9f0140..078b723 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,7 +393,7 @@ dependencies = [ [[package]] name = "etradeTaxReturnHelper" -version = "0.3.0" +version = "0.3.1" dependencies = [ "calamine", "chrono", diff --git a/Cargo.toml b/Cargo.toml index b4551e8..c15e810 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "etradeTaxReturnHelper" -version = "0.3.0" +version = "0.3.1" edition = "2021" description = "Parses etrade financial documents for transaction details (income, tax paid, cost basis) and compute total income and total tax paid according to chosen tax residency (currency)" license = "BSD-3-Clause" diff --git a/src/gui.rs b/src/gui.rs index 8ce0574..34b9ead 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -79,7 +79,7 @@ pub mod gui { let mut file_names: Vec = vec![]; let list_names = browser.borrow(); log::info!("Processing {} files", list_names.size()); - for i in 1..list_names.size() + 1 { + for i in 1..=list_names.size() { let line_content = browser.borrow().text(i); match line_content { Some(text) => { @@ -92,10 +92,16 @@ pub mod gui { } } } + buffer.set_text(""); + tbuffer.set_text(""); + nbuffer.set_text("Running..."); let rd: Box = Box::new(PL {}); let (gross_div, tax_div, gross_sold, cost_sold, div_transactions, sold_transactions) = match run_taxation(&rd, file_names) { - Ok((gd, td, gs, cs, dts, sts)) => (gd, td, gs, cs, dts, sts), + Ok((gd, td, gs, cs, dts, sts)) => { + nbuffer.set_text("Finished.\n\n (Double check if generated tax data (Summary) makes sense and then copy it to your tax form)"); + (gd, td, gs, cs, dts, sts) + } Err(err) => { nbuffer.set_text(&err); panic!("Error: unable to perform taxation"); @@ -217,7 +223,13 @@ pub mod gui { let mut pack3 = Pack::new(0, 0, SUMMARY_COL_WIDTH, 300, ""); pack3.set_type(fltk::group::PackType::Vertical); - let mut frame3 = Frame::new(0, 0, SUMMARY_COL_WIDTH, 30, "Summary"); + let mut frame3 = Frame::new( + 0, + 0, + SUMMARY_COL_WIDTH, + 30, + "Summary (Data for your Tax form)", + ); frame3.set_frame(FrameType::EngravedFrame); let buffer = TextBuffer::default(); diff --git a/src/lib.rs b/src/lib.rs index 73f36d2..930423e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,28 +109,31 @@ pub trait Residency { let base_exchange_rate_url = "https://www.exchange-rates.org/Rate/"; - dates.iter_mut().for_each(|(date, val)| { - let mut converted_date = chrono::NaiveDate::parse_from_str(&date, "%m/%d/%y").unwrap(); + dates.iter_mut().try_for_each(|(date, val)| { + let mut converted_date = chrono::NaiveDate::parse_from_str(&date, "%m/%d/%y") + .map_err(|x| format!("Unable to convert date {x}"))?; converted_date = converted_date .checked_sub_signed(chrono::Duration::days(1)) - .expect_and_log("Error traversing date"); + .ok_or("Error traversing date")?; let exchange_rate_url: String = base_exchange_rate_url.to_string() + &format!("{}/{}/{}", from, to, converted_date.format("%m-%d-%Y")) + "/?format=json"; let body = client.get(&(exchange_rate_url)).send(); - let actual_body = body.expect_and_log(&format!( - "Getting Exchange Rate from Exchange-Rates.org ({}) failed", - exchange_rate_url - )); + let actual_body = body.map_err(|_| { + format!( + "Getting Exchange Rate from Exchange-Rates.org ({}) failed", + exchange_rate_url + ) + })?; if actual_body.status().is_success() { log::info!("RESPONSE {:#?}", actual_body); let exchange_rates_response = actual_body .text() - .expect_and_log("Error converting response to Text"); + .map_err(|_| "Error converting response to Text")?; log::info!("body of exchange_rate = {:#?}", &exchange_rates_response); // parsing text response if let Ok((exchange_rate, exchange_rate_date)) = @@ -138,10 +141,11 @@ pub trait Residency { { *val = Some((exchange_rate_date, exchange_rate)); } + Ok(()) } else { - panic!("Error getting exchange rate"); + return Err("Error getting exchange rate".to_string()); } - }); + })?; Ok(()) } @@ -198,18 +202,12 @@ pub fn run_taxation( } }); // 2. Verify Transactions - match verify_dividends_transactions(&parsed_div_transactions) { - Ok(()) => log::info!("Dividends transactions are consistent"), - Err(msg) => { - println!("{}", msg); - log::warn!("{}", msg); - } - } + verify_dividends_transactions(&parsed_div_transactions)?; + log::info!("Dividends transactions are consistent"); // 3. Verify and create full sold transactions info needed for TAX purposes let detailed_sold_transactions = - reconstruct_sold_transactions(&parsed_sold_transactions, &parsed_gain_and_losses) - .expect_and_log("Error reconstructing detailed sold transactions."); + reconstruct_sold_transactions(&parsed_sold_transactions, &parsed_gain_and_losses)?; // 4. Get Exchange rates // Gather all trade , settlement and transaction dates into hash map to be passed to @@ -238,8 +236,7 @@ pub fn run_taxation( }, ); - rd.get_exchange_rates(&mut dates) - .expect_and_log("Error: unable to get exchange rates"); + rd.get_exchange_rates(&mut dates).map_err(|x| "Error: unable to get exchange rates. Please check your internet connection or proxy settings\n\nDetails:".to_string()+&x)?; // Make a detailed_div_transactions let transactions = create_detailed_div_transactions(parsed_div_transactions, &dates); diff --git a/src/main.rs b/src/main.rs index 7577195..9f70805 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,12 @@ fn main() { let pdfnames: Vec = pdfnames.map(|x| x.to_string()).collect(); - let (gross_div, tax_div, gross_sold, cost_sold, _, _) = run_taxation(&rd, pdfnames).unwrap(); + let (gross_div, tax_div, gross_sold, cost_sold) = match run_taxation(&rd, pdfnames) { + Ok((gross_div, tax_div, gross_sold, cost_sold, _, _)) => { + (gross_div, tax_div, gross_sold, cost_sold) + } + Err(msg) => panic!("\nError: Unable to compute taxes. \n\nDetails: {msg}"), + }; let presentation = rd.present_result(gross_div, tax_div, gross_sold, cost_sold); presentation.iter().for_each(|x| println!("{x}")); @@ -242,7 +247,7 @@ mod tests { ); Ok(()) } - Err(x) => panic!("Error in taxation process"), + Err(x) => panic!("Error in taxation process: {x}"), } } diff --git a/src/pl.rs b/src/pl.rs index 58cae71..2edc594 100644 --- a/src/pl.rs +++ b/src/pl.rs @@ -41,22 +41,26 @@ impl etradeTaxReturnHelper::Residency for PL { // If there is proxy then pick first URL let base_client = ReqwestClient::builder(); let client = match &http_proxy { - Ok(proxy) => base_client - .proxy(reqwest::Proxy::http(proxy).expect_and_log("Error setting HTTP proxy")), + Ok(proxy) => base_client.proxy( + reqwest::Proxy::http(proxy) + .map_err(|x| format!("Error setting HTTP proxy. \nDetails: {}", x))?, + ), Err(_) => base_client, }; let client = match &https_proxy { - Ok(proxy) => client - .proxy(reqwest::Proxy::https(proxy).expect_and_log("Error setting HTTP proxy")), + Ok(proxy) => client.proxy( + reqwest::Proxy::https(proxy) + .map_err(|x| format!("Error setting HTTPS proxy. \nDetails: {}", x))?, + ), Err(_) => client, }; let client = client .build() - .expect_and_log("Could not create REST API client"); + .map_err(|_| "Could not create REST API client")?; let base_exchange_rate_url = "https://api.nbp.pl/api/exchangerates/rates/a/"; - dates.iter_mut().for_each(|(date, val)| { + dates.iter_mut().try_for_each(|(date, val)| { let mut converted_date = chrono::NaiveDate::parse_from_str(&date, "%m/%d/%y").unwrap(); // Try to get exchange rate going backwards with dates till success @@ -64,31 +68,34 @@ impl etradeTaxReturnHelper::Residency for PL { while is_success == false { converted_date = converted_date .checked_sub_signed(chrono::Duration::days(1)) - .expect_and_log("Error traversing date"); + .ok_or("Error traversing date")?; let exchange_rate_url: String = base_exchange_rate_url.to_string() + &format!("usd/{}", converted_date.format("%Y-%m-%d")) + "/?format=json"; let body = client.get(&(exchange_rate_url)).send(); - let actual_body = body.expect_and_log(&format!( - "Getting Exchange Rate from NBP ({}) failed", - exchange_rate_url - )); + let actual_body = body.map_err(|_| { + format!( + "Getting Exchange Rate from NBP ({}) failed", + exchange_rate_url + ) + })?; is_success = actual_body.status().is_success(); if is_success == true { log::info!("RESPONSE {:#?}", actual_body); let nbp_response = actual_body .json::>() - .expect_and_log("Error converting response to JSON"); + .map_err(|_| "Error: getting exchange rate from NBP")?; log::info!("body of exchange_rate = {:#?}", nbp_response); let exchange_rate = nbp_response.rates[0].mid; let exchange_rate_date = format!("{}", converted_date.format("%Y-%m-%d")); *val = Some((exchange_rate_date, exchange_rate)); }; } - }); + Ok::<(), String>(()) + })?; Ok(()) } diff --git a/src/transactions.rs b/src/transactions.rs index 28bd381..234ca1d 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -26,8 +26,7 @@ pub fn verify_dividends_transactions( .unwrap() .year(); if tr_year != transaction_year { - let msg: &str = - "WARNING! Brokerage statements are related to different years. Was it intentional?"; + let msg: &str = "Error: Brokerage statements are related to different years!"; verification = Err(msg.to_owned()); } }); @@ -51,8 +50,8 @@ pub fn reconstruct_sold_transactions( let mut detailed_sold_transactions: Vec<(String, String, String, f32, f32)> = vec![]; if sold_transactions.len() > 0 && gains_and_losses.is_empty() { - panic!("\n\nERROR: Sold transaction detected, but corressponding Gain&Losses document is missing. Please download Gain&Losses XLSX document at:\n - https://us.etrade.com/etx/sp/stockplan#/myAccount/gainsLosses\n\n"); + return Err("\n\nERROR: Sold transaction detected, but corressponding Gain&Losses document is missing. Please download Gain&Losses XLSX document at:\n + https://us.etrade.com/etx/sp/stockplan#/myAccount/gainsLosses\n\n".to_string()); } // iterate through all sold transactions and update it with needed info @@ -492,7 +491,6 @@ mod tests { } #[test] - #[should_panic] fn test_sold_transaction_reconstruction_no_gains_fail() { let parsed_sold_transactions: Vec<(String, String, i32, f32, f32)> = vec![ ( @@ -513,6 +511,9 @@ mod tests { let parsed_gains_and_losses: Vec<(String, String, f32, f32, f32)> = vec![]; - let _ = reconstruct_sold_transactions(&parsed_sold_transactions, &parsed_gains_and_losses); + let result = + reconstruct_sold_transactions(&parsed_sold_transactions, &parsed_gains_and_losses); + assert_eq!( result , Err("\n\nERROR: Sold transaction detected, but corressponding Gain&Losses document is missing. Please download Gain&Losses XLSX document at:\n + https://us.etrade.com/etx/sp/stockplan#/myAccount/gainsLosses\n\n".to_string())); } }