Created accounting journal entry report.

This commit is contained in:
scoobybejesus 2019-11-24 01:32:08 -05:00
parent 55403fb4a9
commit e0439325e2
10 changed files with 362 additions and 8 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "cryptools" name = "cryptools"
version = "0.8.1" version = "0.8.2"
authors = ["scoobybejesus <scoobybejesus@users.noreply.github.com>"] authors = ["scoobybejesus <scoobybejesus@users.noreply.github.com>"]
edition = "2018" edition = "2018"
description = "Command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'." description = "Command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'."

View File

@ -26,6 +26,14 @@ impl RawAccount {
pub fn is_home_currency(&self, compare: &String) -> bool { pub fn is_home_currency(&self, compare: &String) -> bool {
&self.ticker == compare &self.ticker == compare
} }
pub fn margin_string(&self) -> String {
if self.is_margin {
"Margin".to_string()
} else {
"Non-margin".to_string()
}
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@ -32,6 +32,7 @@ pub struct ImportProcessParameters {
pub input_file_has_iso_date_style: bool, pub input_file_has_iso_date_style: bool,
pub should_export: bool, pub should_export: bool,
pub print_menu: bool, pub print_menu: bool,
pub journal_entry_export: bool,
} }
pub fn import_and_process_final( pub fn import_and_process_final(

View File

@ -223,6 +223,8 @@ impl Transaction {
let auto_memo = if self.action_record_idx_vec.len() == 2 { let auto_memo = if self.action_record_idx_vec.len() == 2 {
let tx_type = self.transaction_type(ars, raw_accts, acct_map)?;
let marginness = self.marginness(ars, raw_accts, acct_map); let marginness = self.marginness(ars, raw_accts, acct_map);
if (marginness == TxHasMargin::NoARs) | (marginness == TxHasMargin::TwoARs) { if (marginness == TxHasMargin::NoARs) | (marginness == TxHasMargin::TwoARs) {
@ -239,9 +241,13 @@ impl Transaction {
let ic_raw_acct = raw_accts.get(&ic_acct.raw_key).unwrap(); let ic_raw_acct = raw_accts.get(&ic_acct.raw_key).unwrap();
let ic_ticker = &ic_raw_acct.ticker; let ic_ticker = &ic_raw_acct.ticker;
if tx_type == TxType::Exchange {
format!("Paid {} {} for {} {}, valued at {} {}.", format!("Paid {} {} for {} {}, valued at {} {}.",
og_amt, og_ticker, ic_amt, ic_ticker, self.proceeds, home_currency) og_amt, og_ticker, ic_amt, ic_ticker, self.proceeds, home_currency)
} else {
format!("Transferred {} {} to another account. Received {} {}, likely after a transaction fee.",
og_amt, og_ticker, ic_amt, ic_ticker)
}
} else { } else {
format!("Margin profit or loss valued at {} {}.", self.proceeds, home_currency) format!("Margin profit or loss valued at {} {}.", self.proceeds, home_currency)
@ -286,6 +292,17 @@ impl ActionRecord {
else { Polarity::Incoming } else { Polarity::Incoming }
} }
pub fn cost_basis_in_ar(&self) -> d128 {
let mut cb = d128!(0);
for mvmt in self.movements.borrow().iter() {
cb += mvmt.cost_basis.get()
}
cb.abs()
}
// pub fn is_quote_acct_for_margin_exch( // pub fn is_quote_acct_for_margin_exch(
// &self, // &self,
// raw_accts: &HashMap<u16, RawAccount>, // raw_accts: &HashMap<u16, RawAccount>,

View File

@ -9,6 +9,7 @@ use crptls::account::{Account, RawAccount};
use crptls::core_functions::{ImportProcessParameters}; use crptls::core_functions::{ImportProcessParameters};
use crate::export_csv; use crate::export_csv;
use crate::export_txt; use crate::export_txt;
use crate::export_je;
pub fn export( pub fn export(
@ -85,5 +86,15 @@ pub fn export(
&account_map, &account_map,
)?; )?;
if !settings.lk_treatment_enabled {
export_je::prepare_non_lk_journal_entries(
&settings,
&action_records_map,
&raw_acct_map,
&account_map,
&transactions_map,
)?;
}
Ok(()) Ok(())
} }

View File

@ -622,7 +622,6 @@ pub fn _6_transaction_mvmt_detail_to_csv_w_orig(
let mut orig_proc = mvmt.proceeds.get(); let mut orig_proc = mvmt.proceeds.get();
let mut orig_cost = mvmt.cost_basis.get(); let mut orig_cost = mvmt.cost_basis.get();
let mut orig_gain_loss = mvmt.get_orig_gain_or_loss(); let mut orig_gain_loss = mvmt.get_orig_gain_or_loss();
println!("{:?}", expense);
if tx_type == TxType::Flow && amount > d128!(0) { if tx_type == TxType::Flow && amount > d128!(0) {
proceeds_lk = d128!(0); proceeds_lk = d128!(0);

292
src/export_je.rs Normal file
View File

@ -0,0 +1,292 @@
// Copyright (c) 2017-2019, scoobybejesus
// Redistributions must include the license: https://github.com/scoobybejesus/cryptools/blob/master/LEGAL.txt
use std::fs::{OpenOptions};
use std::collections::{HashMap};
use std::path::PathBuf;
use std::error::Error;
use std::io::prelude::Write;
use decimal::d128;
use crptls::transaction::{Transaction, ActionRecord, Polarity, TxType};
use crptls::account::{Account, RawAccount, Term};
use crptls::core_functions::{ImportProcessParameters};
pub fn prepare_non_lk_journal_entries(
settings: &ImportProcessParameters,
ars: &HashMap<u32, ActionRecord>,
raw_acct_map: &HashMap<u16, RawAccount>,
acct_map: &HashMap<u16, Account>,
txns_map: &HashMap<u32, Transaction>,
) -> Result<(), Box<dyn Error>> {
let file_name = PathBuf::from("J1_Journal_Entries.txt");
let path = PathBuf::from(&settings.export_path.clone());
let full_path: PathBuf = [path, file_name].iter().collect();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(full_path)?;
writeln!(file, "Journal Entries
\nCosting method used: {}.
Home currency: {}
Enable like-kind treatment: {}",
settings.costing_method,
settings.home_currency,
settings.lk_treatment_enabled
)?;
if settings.lk_treatment_enabled {
writeln!(file, "Like-kind cut-off date: {}.",
settings.lk_cutoff_date
)?;
}
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 date = txn.date;
let user_memo = txn.user_memo.to_string();
let auto_memo = txn.get_auto_memo(ars, raw_acct_map,acct_map, &settings.home_currency)?;
let tx_type = txn.transaction_type(&ars, &raw_acct_map, &acct_map)?;
writeln!(file, "\n=====================================\n")?;
writeln!(file, "Txn {} on {}. {}. {}",
txn_num,
date,
user_memo,
auto_memo,
)?;
let mut cost_basis_ic: Option<d128> = None;
let mut cost_basis_og: Option<d128> = None;
let mut acct_string_ic = "".to_string();
let mut acct_string_og = "".to_string();
for ar_num in txn.action_record_idx_vec.iter() {
let ar = ars.get(ar_num).unwrap();
let acct = acct_map.get(&ar.account_key).unwrap();
let raw_acct = raw_acct_map.get(&acct.raw_key).unwrap();
if ar.direction() == Polarity::Incoming {
cost_basis_ic = Some(ar.cost_basis_in_ar());
acct_string_ic = format!("{} - {} ({}) (#{})",
raw_acct.name,
raw_acct.ticker,
raw_acct.margin_string(),
raw_acct.account_num,
);
} else {
cost_basis_og = Some(ar.cost_basis_in_ar());
acct_string_og = format!("{} - {} ({}) (#{})",
raw_acct.name,
raw_acct.ticker,
raw_acct.margin_string(),
raw_acct.account_num,
);
}
}
let mut term_st: Option<Term> = None;
let mut term_lt: Option<Term> = None;
let mut polarity: Option<Polarity> = None;
let mut amount_st = d128!(0);
let mut proceeds_st = d128!(0);
let mut cost_basis_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 = d128!(0);
let mut expense = d128!(0);
let flow_or_outgoing_exchange_movements = txn.get_outgoing_exchange_and_flow_mvmts(
&settings.home_currency,
ars,
raw_acct_map,
acct_map,
txns_map
)?;
for mvmt in flow_or_outgoing_exchange_movements.iter() {
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_lk.get();
cost_basis_lt += mvmt.cost_basis_lk.get();
match term_lt {
None => { term_lt = Some(term)}
_ => {}
}
} else {
assert_eq!(term, Term::ST);
amount_st += mvmt.amount;
proceeds_st += mvmt.proceeds_lk.get();
cost_basis_st += mvmt.cost_basis_lk.get();
if term_st == None {
term_st = Some(term);
}
}
income += mvmt.get_income(ars, &raw_acct_map, &acct_map, &txns_map)?;
expense += mvmt.get_expense(ars, &raw_acct_map, &acct_map, &txns_map)?;
}
if (txn.transaction_type(
ars,
&raw_acct_map,
&acct_map)? == TxType::Flow
) & (polarity == Some(Polarity::Incoming)) {
proceeds_st = d128!(0);
cost_basis_st = d128!(0);
proceeds_lt = d128!(0);
cost_basis_lt = d128!(0);
}
let lt_gain_loss = proceeds_lt + cost_basis_lt;
let st_gain_loss = proceeds_st + cost_basis_st;
let mut debits = d128!(0);
let mut credits = d128!(0);
if let Some(cb) = cost_basis_ic {
debits += cb;
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
acct_string_ic,
"",
cb.to_string(),
"",
"",
)?;
}
if let Some(cb) = cost_basis_og {
credits += cb;
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
acct_string_og,
"",
"",
"",
cb.to_string(),
)?;
}
if lt_gain_loss != d128!(0) {
if lt_gain_loss > d128!(0) {
credits += lt_gain_loss.abs();
let ltg_string = format!("Long-term gain disposing {}", amount_lt.abs());
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
ltg_string,
"",
"",
"",
lt_gain_loss.to_string(),
)?;
} else {
debits += lt_gain_loss.abs();
let ltl_string = format!("Long-term loss disposing {}", amount_lt.abs());
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
ltl_string,
"",
lt_gain_loss.abs().to_string(),
"",
"",
)?;
}
}
if st_gain_loss != d128!(0) {
if st_gain_loss > d128!(0) {
credits += st_gain_loss.abs();
let stg_string = format!("Short-term gain disposing {}", amount_st.abs());
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
stg_string,
"",
"",
"",
st_gain_loss.to_string(),
)?;
} else {
debits += st_gain_loss.abs();
let stl_string = format!("Short-term loss disposing {}", amount_st.abs());
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
stl_string,
"",
st_gain_loss.abs().to_string(),
"",
"",
)?;
}
}
if income != d128!(0) {
credits += income;
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
"Income",
"",
"",
"",
income.to_string(),
)?;
}
if expense != d128!(0) {
debits += expense.abs();
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
"Expense",
"",
expense.abs().to_string(),
"",
"",
)?;
}
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
"",
"",
"--------------------",
"",
"--------------------",
)?;
writeln!(file, "{:50}{:5}{:>20}{:5}{:>20}",
" Totals",
"",
debits,
"",
credits,
)?;
// if (debits - credits) != d128!(0) {
// println!("Rounding issue on transaction #{}", txn_num);
// }
}
Ok(())
}

View File

@ -379,3 +379,5 @@ Enable like-kind treatment: {}",
Ok(()) Ok(())
} }

View File

@ -21,6 +21,7 @@ mod skip_wizard;
mod mytui; mod mytui;
mod export_csv; mod export_csv;
mod export_txt; mod export_txt;
mod export_je;
mod export_all; mod export_all;
mod tests; mod tests;
@ -48,6 +49,13 @@ pub struct Flags {
#[structopt(name = "accept args", short = "a", long = "accept")] #[structopt(name = "accept args", short = "a", long = "accept")]
accept_args: bool, accept_args: bool,
/// This flag will suppress the printing of "all" reports, except that it will trigger the
/// production of a txt file containing an accounting journal entry for every transaction.
/// Individual account and transaction reports may still be printed via the print_menu
/// with the -p flag. The journal entry report is only suitable for non-like-kind activity.
#[structopt(name = "journal entries", short, long = "journal-entries")]
journal_entries_only: bool,
/// This will cause the program to expect the txDate field in the file_to_import to use the format /// This will cause the program to expect the txDate field in the file_to_import to use the format
/// YYYY-MM-dd or YY-MM-dd (or YYYY/MM/dd or YY/MM/dd, depending on the date-separator option) /// YYYY-MM-dd or YY-MM-dd (or YYYY/MM/dd or YY/MM/dd, depending on the date-separator option)
/// instead of the default US-style MM-dd-YYYY or MM-dd-YY (or MM/dd/YYYY or MM/dd/YY, depending on the /// instead of the default US-style MM-dd-YYYY or MM-dd-YY (or MM/dd/YYYY or MM/dd/YY, depending on the
@ -126,8 +134,10 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut should_export_all = settings.should_export; let mut should_export_all = settings.should_export;
let present_print_menu_tui = settings.print_menu; let present_print_menu_tui = settings.print_menu;
let print_journal_entries_only = settings.journal_entry_export;
if present_print_menu_tui { should_export_all = false } if present_print_menu_tui { should_export_all = false }
if print_journal_entries_only { should_export_all = false }
if should_export_all { if should_export_all {
@ -140,6 +150,19 @@ fn main() -> Result<(), Box<dyn Error>> {
)?; )?;
} }
if print_journal_entries_only {
if !settings.lk_treatment_enabled {
export_je::prepare_non_lk_journal_entries(
&settings,
&action_records_map,
&raw_acct_map,
&account_map,
&transactions_map,
)?;
}
}
if present_print_menu_tui { if present_print_menu_tui {
mytui::print_menu_tui::print_menu_tui( mytui::print_menu_tui::print_menu_tui(

View File

@ -73,6 +73,7 @@ pub (crate) fn run_setup(args: super::Cli) -> Result<(PathBuf, ImportProcessPara
should_export: should_export, should_export: should_export,
export_path: output_dir_path, export_path: output_dir_path,
print_menu: args.flags.print_menu, print_menu: args.flags.print_menu,
journal_entry_export: args.flags.journal_entries_only,
}; };
Ok((input_file_path, settings)) Ok((input_file_path, settings))