Skip to content

Commit

Permalink
Interests on cash are reported as separate transactions (#97)
Browse files Browse the repository at this point in the history
* - Added interest rate printing and showing in GUI

* - Interest rate excluded as separate transaction
  • Loading branch information
jczaja authored Mar 1, 2024
1 parent 28696e4 commit 23098f6
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 28 deletions.
9 changes: 6 additions & 3 deletions src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ pub mod gui {
tbuffer.set_text("");
nbuffer.set_text("Running...");
let rd: Box<dyn etradeTaxReturnHelper::Residency> = Box::new(PL {});
let (gross_div, tax_div, gross_sold, cost_sold, div_transactions, revolut_transactions, sold_transactions) =
let (gross_div, tax_div, gross_sold, cost_sold, interests_transactions, div_transactions, revolut_transactions, sold_transactions) =
match run_taxation(&rd, file_names) {
Ok((gd, td, gs, cs, dts, rts, sts)) => {
Ok((gd, td, gs, cs, its, dts, rts, 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, rts, sts)
(gd, td, gs, cs, its, dts, rts, sts)
}
Err(err) => {
nbuffer.set_text(&err);
Expand All @@ -134,6 +134,9 @@ pub mod gui {
nbuffer.set_text(&warn_msg);
}
let mut transactions_strings: Vec<String> = vec![];
interests_transactions
.iter()
.for_each(|x| transactions_strings.push(x.format_to_print("INTERESTS").expect_and_log("Error: Formatting INTERESTS transaction failed")));
div_transactions
.iter()
.for_each(|x| transactions_strings.push(x.format_to_print("DIV").expect_and_log("Error: Formatting DIV transaction failed")));
Expand Down
26 changes: 21 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ type ReqwestClient = reqwest::blocking::Client;

pub use logging::ResultExt;
use transactions::{
create_detailed_div_transactions, create_detailed_revolut_transactions,
create_detailed_sold_transactions, reconstruct_sold_transactions,
verify_dividends_transactions,
create_detailed_div_transactions, create_detailed_interests_transactions,
create_detailed_revolut_transactions, create_detailed_sold_transactions,
reconstruct_sold_transactions, verify_dividends_transactions, verify_interests_transactions,
};

#[derive(Debug, PartialEq, PartialOrd, Copy, Clone)]
Expand Down Expand Up @@ -260,10 +260,12 @@ pub fn run_taxation(
f32,
Vec<Transaction>,
Vec<Transaction>,
Vec<Transaction>,
Vec<SoldTransaction>,
),
String,
> {
let mut parsed_interests_transactions: Vec<(String, f32)> = vec![];
let mut parsed_div_transactions: Vec<(String, f32, f32)> = vec![];
let mut parsed_sold_transactions: Vec<(String, String, f32, f32, f32)> = vec![];
let mut parsed_gain_and_losses: Vec<(String, String, f32, f32, f32)> = vec![];
Expand All @@ -274,7 +276,8 @@ pub fn run_taxation(
// If name contains .pdf then parse as pdf
// if name contains .xlsx then parse as spreadsheet
if x.contains(".pdf") {
let (mut div_t, mut sold_t, _) = pdfparser::parse_statement(x)?;
let (mut int_t, mut div_t, mut sold_t, _) = pdfparser::parse_statement(x)?;
parsed_interests_transactions.append(&mut int_t);
parsed_div_transactions.append(&mut div_t);
parsed_sold_transactions.append(&mut sold_t);
} else if x.contains(".xlsx") {
Expand All @@ -287,6 +290,8 @@ pub fn run_taxation(
Ok::<(), String>(())
})?;
// 2. Verify Transactions
verify_interests_transactions(&parsed_interests_transactions)?;
log::info!("Interests transactions are consistent");
verify_dividends_transactions(&parsed_div_transactions)?;
log::info!("Dividends transactions are consistent");

Expand All @@ -300,6 +305,14 @@ pub fn run_taxation(
// Hash map : Key(event date) -> (preceeding date, exchange_rate)
let mut dates: std::collections::HashMap<Exchange, Option<(String, f32)>> =
std::collections::HashMap::new();
parsed_interests_transactions
.iter()
.for_each(|(trade_date, _)| {
let ex = Exchange::USD(trade_date.clone());
if dates.contains_key(&ex) == false {
dates.insert(ex, None);
}
});
parsed_div_transactions
.iter()
.for_each(|(trade_date, _, _)| {
Expand Down Expand Up @@ -340,19 +353,22 @@ pub fn run_taxation(
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.as_str())?;

// Make a detailed_div_transactions
let interests = create_detailed_interests_transactions(parsed_interests_transactions, &dates)?;
let transactions = create_detailed_div_transactions(parsed_div_transactions, &dates)?;
let sold_transactions = create_detailed_sold_transactions(detailed_sold_transactions, &dates)?;
let revolut_transactions =
create_detailed_revolut_transactions(parsed_revolut_transactions, &dates)?;

let (gross_interests, _) = compute_div_taxation(&interests);
let (gross_div, tax_div) = compute_div_taxation(&transactions);
let (gross_sold, cost_sold) = compute_sold_taxation(&sold_transactions);
let (gross_revolut, cost_revolut) = compute_div_taxation(&revolut_transactions);
Ok((
gross_div,
tax_div,
gross_sold + gross_revolut, // We put sold and savings income into the same column
gross_interests + gross_sold + gross_revolut, // We put sold and savings income into the same column
cost_sold + cost_revolut,
interests,
transactions,
revolut_transactions,
sold_transactions,
Expand Down
15 changes: 8 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod gui;
use etradeTaxReturnHelper::run_taxation;
use logging::ResultExt;

// TODO: make UT working and test GUI
// TODO: Make a parsing of incomplete date
// TODO: Dividends of revolut should combined with dividends not sold
// TODO: When I sold on Dec there was EST cost (0.04). Make sure it is included in your results
Expand Down Expand Up @@ -78,7 +79,7 @@ fn main() {
let pdfnames: Vec<String> = pdfnames.map(|x| x.to_string()).collect();

let (gross_div, tax_div, gross_sold, cost_sold) = match run_taxation(&rd, pdfnames) {
Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _)) => {
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}"),
Expand Down Expand Up @@ -286,7 +287,7 @@ mod tests {
let pdfnames: Vec<String> = pdfnames.map(|x| x.to_string()).collect();

match etradeTaxReturnHelper::run_taxation(&rd, pdfnames) {
Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _)) => {
Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _, _)) => {
assert_eq!(
(gross_div, tax_div, gross_sold, cost_sold),
(14062.57, 2109.3772, 395.45355, 91.156715)
Expand Down Expand Up @@ -317,7 +318,7 @@ mod tests {
let pdfnames: Vec<String> = pdfnames.map(|x| x.to_string()).collect();

match etradeTaxReturnHelper::run_taxation(&rd, pdfnames) {
Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _)) => {
Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _, _)) => {
assert_eq!(
(gross_div, tax_div, gross_sold, cost_sold),
(2930.206, 439.54138, 395.45355, 91.156715)
Expand All @@ -330,7 +331,7 @@ mod tests {

#[test]
#[ignore]
fn test_sold_dividends_taxation_2023() -> Result<(), clap::Error> {
fn test_sold_dividends_interests_taxation() -> Result<(), clap::Error> {
// Get all brokerage with dividends only
let myapp = App::new("E-trade tax helper").setting(AppSettings::ArgRequiredElseHelp);
let rd: Box<dyn etradeTaxReturnHelper::Residency> = Box::new(pl::PL {});
Expand All @@ -353,10 +354,10 @@ mod tests {
let pdfnames: Vec<String> = pdfnames.map(|x| x.to_string()).collect();

match etradeTaxReturnHelper::run_taxation(&rd, pdfnames) {
Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _)) => {
Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _, _)) => {
assert_eq!(
(gross_div, tax_div, gross_sold, cost_sold),
(8369.726, 1253.2899, 14983.293, 7701.9253)
(8355.114, 1253.2899, 14997.904, 7701.9253)
);
Ok(())
}
Expand All @@ -380,7 +381,7 @@ mod tests {
let pdfnames: Vec<String> = pdfnames.map(|x| x.to_string()).collect();

match etradeTaxReturnHelper::run_taxation(&rd, pdfnames) {
Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _)) => {
Ok((gross_div, tax_div, gross_sold, cost_sold, _, _, _, _)) => {
assert_eq!(
(gross_div, tax_div, gross_sold, cost_sold),
(3272.3125, 490.82773, 0.0, 0.0),
Expand Down
63 changes: 50 additions & 13 deletions src/pdfparser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ enum StatementType {

#[derive(Clone, Debug, PartialEq)]
enum TransactionType {
Interests,
Dividends,
Sold,
Tax,
Expand Down Expand Up @@ -150,7 +151,7 @@ fn create_tax_parsing_sequence(sequence: &mut std::collections::VecDeque<Box<dyn
sequence.push_back(Box::new(F32Entry { val: 0.0 })); // Tax Entry
}

fn create_dividend_fund_parsing_sequence(
fn create_interests_fund_parsing_sequence(
sequence: &mut std::collections::VecDeque<Box<dyn Entry>>,
) {
sequence.push_back(Box::new(StringEntry {
Expand Down Expand Up @@ -369,6 +370,7 @@ fn recognize_statement(page: PageRc) -> Result<StatementType, String> {
}

fn process_transaction(
interests_transactions: &mut Vec<(String, f32)>,
div_transactions: &mut Vec<(String, f32, f32)>,
sold_transactions: &mut Vec<(String, String, f32, f32, f32)>,
actual_string: &pdf::primitive::PdfString,
Expand Down Expand Up @@ -422,6 +424,21 @@ fn process_transaction(
subject_to_tax.2 = tax_us;
log::info!("Completed parsing Tax transaction");
}
TransactionType::Interests => {
let gross_us = transaction
.next()
.unwrap()
.getf32()
.ok_or("Processing of Interests transaction went wrong")?;

interests_transactions.push((
transaction_dates
.pop()
.ok_or("Error: missing transaction dates when parsing")?,
gross_us,
));
log::info!("Completed parsing Dividend transaction");
}
TransactionType::Dividends => {
let gross_us = transaction
.next()
Expand Down Expand Up @@ -470,6 +487,7 @@ fn parse_brokerage_statement<'a, I>(
pages_iter: I,
) -> Result<
(
Vec<(String, f32)>,
Vec<(String, f32, f32)>,
Vec<(String, String, f32, f32, f32)>,
Vec<(String, String, i32, f32, f32, f32, f32, f32)>,
Expand Down Expand Up @@ -571,6 +589,9 @@ where
TransactionType::Tax => {
return Err("TransactionType::Tax should not appear during brokerage statement processing!".to_string());
}
TransactionType::Interests => {
return Err("TransactionType::Interest rate should not appear during brokerage statement processing!".to_string());
}
TransactionType::Dividends => {
let tax_us = transaction.next().unwrap().getf32().expect_and_log("Processing of Dividend transaction went wrong");
let gross_us = transaction.next().unwrap().getf32().expect_and_log("Processing of Dividend transaction went wrong");
Expand Down Expand Up @@ -644,7 +665,7 @@ where
}
}
}
Ok((div_transactions, sold_transactions, trades))
Ok((vec![], div_transactions, sold_transactions, trades))
}

fn check_if_transaction(
Expand All @@ -661,9 +682,9 @@ fn check_if_transaction(
year.ok_or("Missing year that should be parsed before transactions".to_owned())?;

if candidate_string == "DIVIDEND" {
create_dividend_fund_parsing_sequence(sequence);
state = ParserState::ProcessingTransaction(TransactionType::Dividends);
log::info!("Starting to parse Dividend Fund transaction");
create_interests_fund_parsing_sequence(sequence);
state = ParserState::ProcessingTransaction(TransactionType::Interests);
log::info!("Starting to parse Interests Fund transaction");
} else if candidate_string == "QUALIFIED DIVIDEND" {
create_qualified_dividend_parsing_sequence(sequence);
state = ParserState::ProcessingTransaction(TransactionType::Dividends);
Expand Down Expand Up @@ -708,6 +729,7 @@ fn parse_account_statement<'a, I>(
pages_iter: I,
) -> Result<
(
Vec<(String, f32)>,
Vec<(String, f32, f32)>,
Vec<(String, String, f32, f32, f32)>,
Vec<(String, String, i32, f32, f32, f32, f32, f32)>,
Expand All @@ -717,6 +739,7 @@ fn parse_account_statement<'a, I>(
where
I: Iterator<Item = Result<PageRc, pdf::error::PdfError>>,
{
let mut interests_transactions: Vec<(String, f32)> = vec![];
let mut div_transactions: Vec<(String, f32, f32)> = vec![];
let mut sold_transactions: Vec<(String, String, f32, f32, f32)> = vec![];
let trades: Vec<(String, String, i32, f32, f32, f32, f32, f32)> = vec![];
Expand Down Expand Up @@ -778,6 +801,7 @@ where
}
ParserState::ProcessingTransaction(transaction_type) => {
state = process_transaction(
&mut interests_transactions,
&mut div_transactions,
&mut sold_transactions,
&actual_string,
Expand All @@ -799,10 +823,16 @@ where
}
}

Ok((div_transactions, sold_transactions, trades))
Ok((
interests_transactions,
div_transactions,
sold_transactions,
trades,
))
}
/// This function parses given PDF document
/// and returns result of parsing which is a tuple of
/// interest rate transactions
/// found Dividends paid transactions (div_transactions),
/// Sold stock transactions (sold_transactions)
/// information on transactions in case of parsing trade document (trades)
Expand All @@ -814,6 +844,7 @@ pub fn parse_statement(
pdftoparse: &str,
) -> Result<
(
Vec<(String, f32)>,
Vec<(String, f32, f32)>,
Vec<(String, String, f32, f32, f32)>,
Vec<(String, String, i32, f32, f32, f32, f32, f32)>,
Expand All @@ -835,7 +866,8 @@ pub fn parse_statement(

let document_type = recognize_statement(first_page)?;

let (div_transactions, sold_transactions, trades) = match document_type {
let (interests_transactions, div_transactions, sold_transactions, trades) = match document_type
{
StatementType::BrokerageStatement => {
log::info!("Processing brokerage statement PDF");
parse_brokerage_statement(pdffile_iter)?
Expand All @@ -846,7 +878,12 @@ pub fn parse_statement(
}
};

Ok((div_transactions, sold_transactions, trades))
Ok((
interests_transactions,
div_transactions,
sold_transactions,
trades,
))
}

#[cfg(test)]
Expand Down Expand Up @@ -975,7 +1012,7 @@ mod tests {
Some("23".to_owned())
),
Ok(ParserState::ProcessingTransaction(
TransactionType::Dividends
TransactionType::Interests
))
);

Expand Down Expand Up @@ -1025,10 +1062,8 @@ mod tests {
assert_eq!(
parse_statement("data/MS_ClientStatements_6557_202312.pdf"),
(Ok((
vec![
("12/1/23".to_owned(), 1.22, 0.00),
("12/1/23".to_owned(), 386.50, 57.98),
],
vec![("12/1/23".to_owned(), 1.22)],
vec![("12/1/23".to_owned(), 386.50, 57.98),],
vec![(
"12/21/23".to_owned(),
"12/26/23".to_owned(),
Expand All @@ -1048,6 +1083,7 @@ mod tests {
assert_eq!(
parse_statement("data/example-divs.pdf"),
(Ok((
vec![],
vec![("03/01/22".to_owned(), 698.25, 104.74)],
vec![],
vec![]
Expand All @@ -1056,6 +1092,7 @@ mod tests {
assert_eq!(
parse_statement("data/example-sold-wire.pdf"),
Ok((
vec![],
vec![],
vec![(
"05/02/22".to_owned(),
Expand Down
Loading

0 comments on commit 23098f6

Please sign in to comment.