From d1bb1c255e262467e775472642eccf3f10da4c21 Mon Sep 17 00:00:00 2001 From: scoobybejesus Date: Sun, 25 Aug 2019 20:53:18 -0400 Subject: [PATCH] moved InventoryCostingMethod to core_functions; resolves #11 --- src/cli_user_choices.rs | 277 +++++++++++++++++++++++++ src/core_functions.rs | 13 ++ src/create_lots_mvmts.rs | 3 +- src/csv_export.rs | 433 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 6 +- 5 files changed, 728 insertions(+), 4 deletions(-) create mode 100644 src/cli_user_choices.rs create mode 100644 src/csv_export.rs diff --git a/src/cli_user_choices.rs b/src/cli_user_choices.rs new file mode 100644 index 0000000..58dfce5 --- /dev/null +++ b/src/cli_user_choices.rs @@ -0,0 +1,277 @@ +// Copyright (c) 2017-2019, scoobybejesus +// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt + +use std::error::Error; +use std::io::{self, BufRead}; +use std::process; +use std::path::PathBuf; + +use chrono::NaiveDate; +use rustyline::completion::{Completer, FilenameCompleter, Pair}; +use rustyline::{CompletionType, Config, Context, EditMode, Editor, Helper}; +use rustyline::config::OutputStreamType; +use rustyline::hint::{Hinter}; +use rustyline::error::ReadlineError; +use rustyline::highlight::{Highlighter}; +use crate::core_functions::InventoryCostingMethod; + +use crate::utils; + + +pub fn choose_file_for_import() -> PathBuf { + + println!("Please input a file (absolute or relative path) to import: "); + + // PathBuf::from("/Users/scoob/Documents/Repos/cryptools-rs/private/RawTxForImport-pycleaned.csv") + + let file_str = _get_path(); + PathBuf::from(file_str.unwrap()) +} + +pub fn choose_export_dir() -> PathBuf { + + println!("Please input a file path for exports: "); + + // PathBuf::from("/Users/scoob/Documents/Testing/rust_exports/") + + let file_str = _get_path(); + PathBuf::from(file_str.unwrap()) +} + +fn _get_path() -> Result<(String), Box> { + + struct MyHelper { + completer: FilenameCompleter, + colored_prompt: String, + } + + impl Completer for MyHelper { + + type Candidate = Pair; + + fn complete( + &self, + line: &str, + pos: usize, + ctx: &Context<'_>, + ) -> Result<(usize, Vec), ReadlineError> { + self.completer.complete(line, pos, ctx) + } + } + + impl Hinter for MyHelper {} + impl Highlighter for MyHelper {} + impl Helper for MyHelper {} + + let h = MyHelper { + completer: FilenameCompleter::new(), + colored_prompt: "".to_owned(), + }; + + let config = Config::builder() + .history_ignore_space(true) + .completion_type(CompletionType::Circular) + .edit_mode(EditMode::Vi) + .output_stream(OutputStreamType::Stdout) + .build(); + + let count = 1; + let mut rl = Editor::with_config(config); + let p = format!("{}> ", count); + rl.set_helper(Some(h)); + rl.helper_mut().unwrap().colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p); + let readline = rl.readline(">> "); + + match readline { + Ok(line) => { + rl.add_history_entry(line.as_str()); + println!(""); + Ok(line) + }, + Err(err) => { + println!("Error during Rustyline: {:?}", err); + process::exit(1) + } + } +} + + +// impl std::convert::From for InventoryCostingMethod { +// fn from(osstr: OsStr) -> InventoryCostingMethod { +// let osstring1 = OsString::from(Box::); +// let new_string = osstr.into_string().expect("Invalid input. Could not convert to string."); +// let method = match new_string.trim() { +// "1" => InventoryCostingMethod::LIFObyLotCreationDate, +// "2" => InventoryCostingMethod::LIFObyLotBasisDate, +// "3" => InventoryCostingMethod::FIFObyLotCreationDate, +// "4" => InventoryCostingMethod::FIFObyLotBasisDate, +// _ => { println!("Invalid choice. Could not convert."); process::exit(1) +// } +// }; +// method +// } +// } + +pub fn choose_inventory_costing_method() -> InventoryCostingMethod { + + println!("Choose the lot inventory costing method. [Default: 1]"); + println!("1. LIFO according to the order the lot was created."); + println!("2. LIFO according to the basis date of the lot."); + println!("3. FIFO according to the order the lot was created."); + println!("4. FIFO according to the basis date of the lot."); + + let method = match _costing_method() { + Ok(x) => {x}, + Err(err) => { process::exit(1) } + }; + + fn _costing_method() -> Result<(InventoryCostingMethod), Box> { + + let mut input = String::new(); + let stdin = io::stdin(); + stdin.lock().read_line(&mut input).expect("Failed to read stdin"); + + match input.trim() { // Without .trim(), there's a hidden \n or something preventing the match + "1" | "" => Ok(InventoryCostingMethod::LIFObyLotCreationDate), + "2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate), + "3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate), + "4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate), + _ => { println!("Invalid choice. Please enter a valid number."); _costing_method() } + } + } + + method +} +pub fn inv_costing_from_cmd_arg(arg: String) -> InventoryCostingMethod { + + let method = match _costing_method(arg) { + Ok(x) => {x}, + Err(err) => { process::exit(1) } + }; + + fn _costing_method(input: String) -> Result<(InventoryCostingMethod), Box> { + + match input.trim() { // Without .trim(), there's a hidden \n or something preventing the match + "1" => Ok(InventoryCostingMethod::LIFObyLotCreationDate), + "2" => Ok(InventoryCostingMethod::LIFObyLotBasisDate), + "3" => Ok(InventoryCostingMethod::FIFObyLotCreationDate), + "4" => Ok(InventoryCostingMethod::FIFObyLotBasisDate), + _ => { println!("Invalid choice. Please enter a valid number."); _costing_method(input) } + } + } + + method +} +pub fn elect_like_kind_treatment(cutoff_date_arg: &Option) -> (bool, String) { + let election: bool; + let date: String; + + if cutoff_date_arg.is_some() { + + let provided_date = NaiveDate::parse_from_str(&cutoff_date_arg.clone().unwrap(), "%y-%m-%d") + .unwrap_or(NaiveDate::parse_from_str(&cutoff_date_arg.clone().unwrap(), "%Y-%m-%d") + .expect("Date entered as -c command line arg has an incorrect format.")); + + println!("\nUse like-kind exchange treatment through {}? [Y/n/c] ('c' to 'change') ", provided_date); + + let (election, date) = match _elect_like_kind_arg(&cutoff_date_arg, provided_date) { + Ok(x) => {x}, + Err(err) => { println!("Fatal error in fn elect_like_kind_treatment()."); process::exit(1) } + }; + + fn _elect_like_kind_arg(cutoff_date_arg: &Option, provided_date: NaiveDate) -> Result<(bool, String), Box> { + + let mut input = String::new(); + let stdin = io::stdin(); + stdin.lock().read_line(&mut input)?; + + + match input.trim().to_ascii_lowercase().as_str() { + "y" | "ye" | "yes" | "" => { + println!(" Using like-kind treatment through {}.\n", provided_date); + Ok( (true, cutoff_date_arg.clone().unwrap()) ) + }, + "n" | "no" => { println!(" Proceeding without like-kind treatment.\n"); + Ok( (false, "1-1-1".to_string()) ) + }, + "c" | "change" => { + println!("Please enter your desired like-kind exchange treatment cutoff date."); + println!(" You must use the format %y-%m-%d (e.g., 2017-12-31, 17-12-31, and 9-6-1 are all acceptable).\n"); + let mut input = String::new(); + let stdin = io::stdin(); + stdin.lock().read_line(&mut input)?; + utils::trim_newline(&mut input); + let newly_chosen_date = NaiveDate::parse_from_str(&input, "%y-%m-%d") + .unwrap_or(NaiveDate::parse_from_str(&input, "%Y-%m-%d") + .expect("Date entered has an incorrect format. Program must abort.")); + // TODO: figure out how to make this fail gracefully and let the user input the date again + println!(" Using like-kind treatment through {}.\n", newly_chosen_date); + Ok( (true, input) ) + }, + _ => { + println!("Please respond with 'y', 'n', or 'c' (or 'yes' or 'no' or 'change')."); + _elect_like_kind_arg(&cutoff_date_arg, provided_date) + } + } + } + + return (election, date) + + } else { + + println!("\nContinue without like-kind exchange treatment? [Y/n] "); + + let (election, date) = match _no_elect_like_kind_arg() { + Ok(x) => {x}, + Err(err) => { println!("Fatal error in like_kind selection. Perhaps incorrect date format."); process::exit(1) } + }; + + fn _no_elect_like_kind_arg() -> Result<(bool, String), Box> { + + let mut input = String::new(); + let stdin = io::stdin(); + stdin.lock().read_line(&mut input)?; + + match input.trim().to_ascii_lowercase().as_str() { + "y" | "ye" | "yes" | "" => { println!(" Proceeding without like-kind treatment.\n"); + Ok( (false, "1-1-1".to_string()) ) + }, + "n" | "no" => { + println!("Please enter your desired like-kind exchange treatment cutoff date."); + println!(" You must use the format %y-%m-%d (e.g., 2017-12-31, 17-12-31, and 9-6-1 are all acceptable).\n"); + let mut input = String::new(); + let stdin = io::stdin(); + stdin.lock().read_line(&mut input)?; + utils::trim_newline(&mut input); + + let newly_chosen_date = NaiveDate::parse_from_str(&input, "%y-%m-%d") + .unwrap_or(NaiveDate::parse_from_str(&input, "%Y-%m-%d") + .expect("Date entered has an incorrect format. Program must abort.")); + // TODO: figure out how to make this fail gracefully and let the user input the date again + println!(" Using like-kind treatment through {}.\n", newly_chosen_date); + + Ok( (true, input) ) + }, + _ => { println!("Please respond with 'y' or 'n' (or 'yes' or 'no')."); _no_elect_like_kind_arg() } + } + } + + return (election, date) + } +} + + +pub struct LotProcessingChoices { + pub export_path: PathBuf, + pub home_currency: String, + pub enable_like_kind_treatment: bool, + pub costing_method: InventoryCostingMethod, + pub lk_cutoff_date_string: String, +} +impl LotProcessingChoices {} + +#[derive(Clone)] +pub struct LikeKindSettings { + pub like_kind_cutoff_date: NaiveDate, + pub like_kind_basis_date_preserved: bool, +} diff --git a/src/core_functions.rs b/src/core_functions.rs index 7fdf388..74e1c20 100644 --- a/src/core_functions.rs +++ b/src/core_functions.rs @@ -8,6 +8,7 @@ use std::process; use std::collections::{HashMap}; use chrono::NaiveDate; +use structopt::StructOpt; use crate::account::{Account, RawAccount, Lot}; use crate::transaction::{Transaction, ActionRecord}; @@ -16,6 +17,18 @@ use crate::import_accts_txns; use crate::import_cost_proceeds_etc; use crate::create_lots_mvmts; +#[derive(Clone, Debug, PartialEq, StructOpt)] +pub enum InventoryCostingMethod { + /// 1. LIFO according to the order the lot was created. + LIFObyLotCreationDate, + /// 2. LIFO according to the basis date of the lot. + LIFObyLotBasisDate, + /// 3. FIFO according to the order the lot was created. + FIFObyLotCreationDate, + /// 4. FIFO according to the basis date of the lot. + FIFObyLotBasisDate, +} + pub fn import_and_process_final( input_file_path: PathBuf, settings: &LotProcessingChoices, diff --git a/src/create_lots_mvmts.rs b/src/create_lots_mvmts.rs index cacd169..981cd2b 100644 --- a/src/create_lots_mvmts.rs +++ b/src/create_lots_mvmts.rs @@ -10,7 +10,8 @@ use chrono::NaiveDate; use crate::transaction::{Transaction, ActionRecord, TxType, Polarity, TxHasMargin}; use crate::account::{Account, RawAccount, Lot, Movement}; -use crate::cli_user_choices::{LotProcessingChoices, InventoryCostingMethod, LikeKindSettings}; +use crate::core_functions::InventoryCostingMethod; +use crate::cli_user_choices::{LotProcessingChoices, LikeKindSettings}; use crate::utils::{round_d128_1e8}; pub fn create_lots_and_movements( diff --git a/src/csv_export.rs b/src/csv_export.rs new file mode 100644 index 0000000..3b43e7a --- /dev/null +++ b/src/csv_export.rs @@ -0,0 +1,433 @@ +// Copyright (c) 2017-2019, scoobybejesus +// Redistributions must include the license: https://github.com/scoobybejesus/cryptools-rs/LEGAL.txt + +use std::rc::{Rc}; +use std::fs::File; +use std::collections::{HashMap}; +use std::path::PathBuf; + +use decimal::d128; + +use crate::transaction::{Transaction, ActionRecord, Polarity, TxType}; +use crate::account::{Account, RawAccount, Term}; +use crate::cli_user_choices::{LotProcessingChoices}; + + +pub fn _1_account_sums_to_csv( + settings: &LotProcessingChoices, + raw_acct_map: &HashMap, + acct_map: &HashMap +) { + + let mut rows: Vec> = [].to_vec(); + let mut header: Vec = [].to_vec(); + + header.extend_from_slice(&[ + "Account".to_string(), + "Balance".to_string(), + "Ticker".to_string(), + "Cost Basis".to_string(), + "Total lots".to_string(), + ]); + rows.push(header); + + let length = acct_map.len(); + + for j in 1..=length { + + let acct = acct_map.get(&(j as u16)).unwrap(); + let mut row: Vec = [].to_vec(); + + let balance: String; + let tentative_balance = acct.get_sum_of_amts_in_lots(); + + if tentative_balance == d128!(0) { + balance = "0.00".to_string() + } else { balance = tentative_balance.to_string() } + + let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap(); + let cost_basis: String; + + if raw_acct.is_margin { cost_basis = "0.00".to_string() } else { + let tentative_cost_basis = acct.get_sum_of_basis_in_lots(); + if tentative_cost_basis == d128!(0) { + cost_basis = "0.00".to_string() + } else { cost_basis = tentative_cost_basis.to_string() } + } + + row.push(raw_acct.name.to_string()); + row.push(balance); + row.push(raw_acct.ticker.to_string()); + row.push(cost_basis); + row.push(acct.list_of_lots.borrow().len().to_string()); + rows.push(row); + } + let file_name = PathBuf::from("1_Acct_Sum_with_cost_basis.csv"); + let path = PathBuf::from(&settings.export_path.clone()); + + let full_path: PathBuf = [path, file_name].iter().collect(); + let buffer = File::create(full_path).unwrap(); + let mut wtr = csv::Writer::from_writer(buffer); + + for row in rows.iter() { + wtr.write_record(row).expect("Could not write row to CSV file"); + } + wtr.flush().expect("Could not flush Writer, though file should exist and be complete"); +} + +pub fn _2_account_sums_nonzero_to_csv( + acct_map: &HashMap, + settings: &LotProcessingChoices, + raw_acct_map: &HashMap +) { + + let mut rows: Vec> = [].to_vec(); + let mut header: Vec = [].to_vec(); + + header.extend_from_slice(&[ + "Account".to_string(), + "Balance".to_string(), + "Ticker".to_string(), + "Cost basis".to_string(), + "Total lots".to_string(), + ]); + rows.push(header); + + let length = acct_map.len(); + + for j in 1..=length { + + let acct = acct_map.get(&(j as u16)).unwrap(); + let mut row: Vec = [].to_vec(); + + let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap(); + let name = raw_acct.name.to_string(); + + let balance: String; + let mut balance_d128 = d128!(0); + let tentative_balance = acct.get_sum_of_amts_in_lots(); + + if tentative_balance == d128!(0) { + balance = "0.00".to_string() + } else { balance_d128 += tentative_balance; balance = tentative_balance.to_string() } + + let cost_basis: String; + + if raw_acct.is_margin { cost_basis = "0.00".to_string() } else { + let tentative_cost_basis = acct.get_sum_of_basis_in_lots(); + if tentative_cost_basis == d128!(0) { + cost_basis = "0.00".to_string() + } else { cost_basis = tentative_cost_basis.to_string() } + } + + if balance_d128 != d128!(0) { + row.push(name); + row.push(balance); + row.push(raw_acct.ticker.to_string()); + row.push(cost_basis); + row.push(acct.list_of_lots.borrow().len().to_string()); + rows.push(row); + } + } + + let file_name = PathBuf::from("2_Acct_Sum_with_nonzero_cost_basis.csv"); + let path = PathBuf::from(&settings.export_path.clone()); + + let full_path: PathBuf = [path, file_name].iter().collect(); + let buffer = File::create(full_path).unwrap(); + let mut wtr = csv::Writer::from_writer(buffer); + + for row in rows.iter() { + wtr.write_record(row).expect("Could not write row to CSV file"); + } + wtr.flush().expect("Could not flush Writer, though file should exist and be complete"); +} + +// pub fn transactions_to_csv( +// transactions: &[Rc], +// ars: &HashMap, +// raw_acct_map: &HashMap, +// acct_map: &HashMap, +// txns_map: &HashMap,) { + +// let mut rows: Vec> = [].to_vec(); +// let mut header: Vec = [].to_vec(); +// header.extend_from_slice(&[ +// "Date".to_string(), +// "Txn#".to_string(), +// "Type".to_string(), +// "Memo".to_string(), +// "Amount".to_string(), +// "Ticker".to_string(), +// "Proceeds".to_string(), +// "Cost basis".to_string(), +// "Gain/loss".to_string(), +// "Term".to_string(), +// "Income".to_string(), +// "Expense".to_string(), +// ]); +// rows.push(header); +// for txn in transactions { +// for mvmt in txn.flow_or_outgoing_exchange_movements.borrow().iter() { +// let lot = mvmt.borrow().get_lot(acct_map, ars); +// let acct = acct_map.get(&lot.account_key).unwrap(); +// let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap(); +// let mut row: Vec = [].to_vec(); +// row.push(txn.date.format("%Y/%m/%d").to_string()); +// row.push(txn.tx_number.to_string()); +// row.push(txn.transaction_type(&ars, &raw_acct_map, &acct_map).to_string()); +// row.push(txn.memo.to_string()); +// row.push(mvmt.borrow().amount.to_string()); +// row.push(raw_acct.ticker.to_string()); +// row.push(mvmt.borrow().proceeds.to_string()); +// row.push(mvmt.borrow().cost_basis.to_string()); +// row.push(mvmt.borrow().get_gain_or_loss().to_string()); +// row.push(mvmt.borrow().get_term(acct_map, ars).to_string()); +// row.push(mvmt.borrow().get_income(ars, &raw_acct_map, &acct_map, &txns_map).to_string()); +// row.push(mvmt.borrow().get_expense(ars, &raw_acct_map, &acct_map, &txns_map).to_string()); +// rows.push(row); +// } +// } +// let buffer = File::create("/Users/scoob/Documents/Testing/rust_exports/test/txns-3rd-try.csv").unwrap(); +// let mut wtr = csv::Writer::from_writer(buffer); +// for row in rows.iter() { +// wtr.write_record(row).expect("Could not write row to CSV file"); +// } +// wtr.flush().expect("Could not flush Writer, though file should exist and be complete"); +// } + +pub fn _5_transaction_mvmt_summaries_to_csv( + settings: &LotProcessingChoices, + ars: &HashMap, + raw_acct_map: &HashMap, + acct_map: &HashMap, + txns_map: &HashMap, +) { + + let mut rows: Vec> = [].to_vec(); + let mut header: Vec = [].to_vec(); + + header.extend_from_slice(&[ + "Date".to_string(), + "Txn#".to_string(), + "Type".to_string(), + "Memo".to_string(), + "Amount".to_string(), + "Ticker".to_string(), + "Term".to_string(), + "Proceeds".to_string(), + "Cost basis".to_string(), + "Gain/loss".to_string(), + "Income".to_string(), + "Expense".to_string(), + ]); + rows.push(header); + + let length = txns_map.len(); + + for txn_num in 1..=length { + + let txn_num = txn_num as u32; + let txn = txns_map.get(&(txn_num)).unwrap(); + let txn_date_string = txn.date.format("%Y/%m/%d").to_string(); + let tx_num_string = txn.tx_number.to_string(); + let tx_type_string = txn.transaction_type(ars, &raw_acct_map, &acct_map).to_string(); + let tx_memo_string = txn.memo.to_string(); + let mut term_st: Option = None; + let mut term_lt: Option = None; + let mut ticker: Option = None; + let mut polarity: Option = None; + + let mut amount_st = d128!(0); + let mut proceeds_st = d128!(0); + let mut cost_basis_st = d128!(0); + + let mut income_st = d128!(0); + let mut expense_st = d128!(0); + + let mut amount_lt = d128!(0); + let mut proceeds_lt = d128!(0); + let mut cost_basis_lt = d128!(0); + + let mut income_lt = d128!(0); + let mut expense_lt = d128!(0); + + let flow_or_outgoing_exchange_movements = txn.get_outgoing_exchange_and_flow_mvmts( + settings, + ars, + raw_acct_map, + acct_map, + txns_map + ); + + for mvmt in flow_or_outgoing_exchange_movements.iter() { + let lot = mvmt.get_lot(acct_map, ars); + let acct = acct_map.get(&lot.account_key).unwrap(); + let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap(); + + if let None = ticker { ticker = Some(raw_acct.ticker.clone()) }; + + if let None = polarity { + polarity = if mvmt.amount > d128!(0) { + Some(Polarity::Incoming) + } else { Some(Polarity::Outgoing) + }; + } + + let term = mvmt.get_term(acct_map, ars); + + if term == Term::LT { + amount_lt += mvmt.amount; + proceeds_lt += mvmt.proceeds.get(); + cost_basis_lt += mvmt.cost_basis.get(); + match term_lt { + None => { term_lt = Some(term)} + _ => {} + } + } else { + assert_eq!(term, Term::ST); + amount_st += mvmt.amount; + proceeds_st += mvmt.proceeds.get(); + cost_basis_st += mvmt.cost_basis.get(); + if term_st == None { + term_st = Some(term); + } + } + } + + if (txn.transaction_type(ars, &raw_acct_map, &acct_map) == TxType::Flow) & (polarity == Some(Polarity::Incoming)) { + // println!("Incoming flow {}", txn.tx_number); + income_st = proceeds_st; + proceeds_st = d128!(0); + cost_basis_st = d128!(0); + income_lt = proceeds_lt; + proceeds_lt = d128!(0); + cost_basis_lt = d128!(0); + } + + if (txn.transaction_type(ars, &raw_acct_map, &acct_map) == TxType::Flow) & (polarity == Some(Polarity::Outgoing)) { + // println!("Outgoing flow {}, proceeds_st {}, proceeds_lt {}", txn.tx_number, proceeds_st, proceeds_lt); + expense_st -= proceeds_st; + expense_lt -= proceeds_lt; + } + + if let Some(term) = term_st { + + let mut row: Vec = [].to_vec(); + + row.push(txn_date_string.clone()); + row.push(tx_num_string.clone()); + row.push(tx_type_string.clone()); + row.push(tx_memo_string.clone()); + row.push(amount_st.to_string()); + row.push(ticker.clone().unwrap()); + row.push(term.abbr_string()); + row.push(proceeds_st.to_string()); + row.push(cost_basis_st.to_string()); + row.push((proceeds_st + cost_basis_st).to_string()); + row.push(income_st.to_string()); + row.push(expense_st.to_string()); + + rows.push(row); + } + if let Some(term) = term_lt { + + let mut row: Vec = [].to_vec(); + + row.push(txn_date_string); + row.push(tx_num_string); + row.push(tx_type_string); + row.push(tx_memo_string); + row.push(amount_lt.to_string()); + row.push(ticker.unwrap()); + row.push(term.abbr_string()); + row.push(proceeds_lt.to_string()); + row.push(cost_basis_lt.to_string()); + row.push((proceeds_lt + cost_basis_lt).to_string()); + row.push(income_lt.to_string()); + row.push(expense_lt.to_string()); + + rows.push(row); + } + } + + let file_name = PathBuf::from("5_Txns_mvmts_summary.csv"); + let path = PathBuf::from(&settings.export_path); + + let full_path: PathBuf = [path, file_name].iter().collect(); + let buffer = File::create(full_path).unwrap(); + let mut wtr = csv::Writer::from_writer(buffer); + + for row in rows.iter() { + wtr.write_record(row).expect("Could not write row to CSV file"); + } + wtr.flush().expect("Could not flush Writer, though file should exist and be complete"); +} + +pub fn accounts_to_csv( + accounts: &[Rc], + ars: &HashMap, + raw_acct_map: &HashMap, + acct_map: &HashMap, + txns_map: &HashMap, +) { + + let mut rows: Vec> = [].to_vec(); + let mut header: Vec = [].to_vec(); + + header.extend_from_slice(&[ + "#".to_string(), + "Account".to_string(), + "Ticker".to_string(), + "Margin".to_string(), + "Date".to_string(), + "Txn#".to_string(), + "Type".to_string(), + "Memo".to_string(), + "Amount".to_string(), + "Proceeds".to_string(), + "Cost basis\n".to_string(), + "Gain/loss".to_string(), + "Term".to_string(), + "Income".to_string(), + "Expense".to_string(), + ]); + rows.push(header); + + for acct in accounts { + for lot in acct.list_of_lots.borrow().iter() { + for mvmt in lot.movements.borrow().iter() { + + let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap(); + let txn = txns_map.get(&mvmt.transaction_key).unwrap(); + let mut row: Vec = [].to_vec(); + + row.push(raw_acct.account_num.to_string()); + row.push(raw_acct.name.to_string()); + row.push(raw_acct.ticker.to_string()); + row.push(raw_acct.is_margin.to_string()); + row.push(mvmt.date.format("%Y/%m/%d").to_string()); + row.push(txn.tx_number.to_string()); + row.push(txn.transaction_type(ars, &raw_acct_map, &acct_map).to_string()); + row.push(txn.memo.to_string()); + row.push(mvmt.amount.to_string()); + row.push(mvmt.proceeds.get().to_string()); + row.push(mvmt.cost_basis.get().to_string()); + row.push(mvmt.get_gain_or_loss().to_string()); + row.push(mvmt.get_term(acct_map, ars).to_string()); + row.push(mvmt.get_income(ars, &raw_acct_map, &acct_map, &txns_map).to_string()); + row.push(mvmt.get_expense(ars, &raw_acct_map, &acct_map, &txns_map).to_string()); + + rows.push(row); + } + } + } + + let buffer = File::create("/Users/scoob/Documents/Testing/rust_exports/test/accts-1st-try.csv").unwrap(); + let mut wtr = csv::Writer::from_writer(buffer); + + for row in rows.iter() { + wtr.write_record(row).expect("Could not write row to CSV file"); + } + wtr.flush().expect("Could not flush Writer, though file should exist and be complete"); +} diff --git a/src/main.rs b/src/main.rs index cd346a0..fc4cf05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -132,7 +132,7 @@ fn main() -> Result<(), Box> { input_file_path = cli_user_choices::choose_file_for_import(); } - costing_method_choice = LotProcessingChoices::choose_inventory_costing_method(); + costing_method_choice = cli_user_choices::choose_inventory_costing_method(); let lk_cutoff_date_opt_string; @@ -142,7 +142,7 @@ fn main() -> Result<(), Box> { lk_cutoff_date_opt_string = None }; - let (like_kind_election, like_kind_cutoff_date) = LotProcessingChoices::elect_like_kind_treatment(&lk_cutoff_date_opt_string); + let (like_kind_election, like_kind_cutoff_date) = cli_user_choices::elect_like_kind_treatment(&lk_cutoff_date_opt_string); settings = LotProcessingChoices { export_path: output_dir_path, @@ -232,7 +232,7 @@ fn main() -> Result<(), Box> { _ => { println!("Invalid choice for inventory costing method. Exiting."); process::exit(0); } } - let costing_method_choice = LotProcessingChoices::inv_costing_from_cmd_arg(args.inv_costing_method.into_string().unwrap()); + let costing_method_choice = cli_user_choices::inv_costing_from_cmd_arg(args.inv_costing_method.into_string().unwrap()); settings = LotProcessingChoices { export_path: output_dir_path,