Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interests on cash are reported as separate transactions #97

Merged
merged 2 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading