Created accounting journal entry report.
This commit is contained in:
parent
55403fb4a9
commit
e0439325e2
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cryptools"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
authors = ["scoobybejesus <scoobybejesus@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
description = "Command-line utility for processing cryptocurrency transactions into 'lots' and 'movements'."
|
||||
|
|
|
@ -26,6 +26,14 @@ impl RawAccount {
|
|||
pub fn is_home_currency(&self, compare: &String) -> bool {
|
||||
&self.ticker == compare
|
||||
}
|
||||
|
||||
pub fn margin_string(&self) -> String {
|
||||
if self.is_margin {
|
||||
"Margin".to_string()
|
||||
} else {
|
||||
"Non-margin".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
@ -32,6 +32,7 @@ pub struct ImportProcessParameters {
|
|||
pub input_file_has_iso_date_style: bool,
|
||||
pub should_export: bool,
|
||||
pub print_menu: bool,
|
||||
pub journal_entry_export: bool,
|
||||
}
|
||||
|
||||
pub fn import_and_process_final(
|
||||
|
|
|
@ -223,6 +223,8 @@ impl Transaction {
|
|||
|
||||
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);
|
||||
|
||||
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_ticker = &ic_raw_acct.ticker;
|
||||
|
||||
if tx_type == TxType::Exchange {
|
||||
format!("Paid {} {} for {} {}, valued at {} {}.",
|
||||
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 {
|
||||
|
||||
format!("Margin profit or loss valued at {} {}.", self.proceeds, home_currency)
|
||||
|
@ -286,6 +292,17 @@ impl ActionRecord {
|
|||
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(
|
||||
// &self,
|
||||
// raw_accts: &HashMap<u16, RawAccount>,
|
||||
|
|
|
@ -9,6 +9,7 @@ use crptls::account::{Account, RawAccount};
|
|||
use crptls::core_functions::{ImportProcessParameters};
|
||||
use crate::export_csv;
|
||||
use crate::export_txt;
|
||||
use crate::export_je;
|
||||
|
||||
|
||||
pub fn export(
|
||||
|
@ -85,5 +86,15 @@ pub fn export(
|
|||
&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(())
|
||||
}
|
|
@ -622,7 +622,6 @@ pub fn _6_transaction_mvmt_detail_to_csv_w_orig(
|
|||
let mut orig_proc = mvmt.proceeds.get();
|
||||
let mut orig_cost = mvmt.cost_basis.get();
|
||||
let mut orig_gain_loss = mvmt.get_orig_gain_or_loss();
|
||||
println!("{:?}", expense);
|
||||
|
||||
if tx_type == TxType::Flow && amount > d128!(0) {
|
||||
proceeds_lk = d128!(0);
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -379,3 +379,5 @@ Enable like-kind treatment: {}",
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -21,6 +21,7 @@ mod skip_wizard;
|
|||
mod mytui;
|
||||
mod export_csv;
|
||||
mod export_txt;
|
||||
mod export_je;
|
||||
mod export_all;
|
||||
mod tests;
|
||||
|
||||
|
@ -48,6 +49,13 @@ pub struct Flags {
|
|||
#[structopt(name = "accept args", short = "a", long = "accept")]
|
||||
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
|
||||
/// 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
|
||||
|
@ -126,8 +134,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
let mut should_export_all = settings.should_export;
|
||||
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 print_journal_entries_only { should_export_all = false }
|
||||
|
||||
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 {
|
||||
|
||||
mytui::print_menu_tui::print_menu_tui(
|
||||
|
|
|
@ -73,6 +73,7 @@ pub (crate) fn run_setup(args: super::Cli) -> Result<(PathBuf, ImportProcessPara
|
|||
should_export: should_export,
|
||||
export_path: output_dir_path,
|
||||
print_menu: args.flags.print_menu,
|
||||
journal_entry_export: args.flags.journal_entries_only,
|
||||
};
|
||||
|
||||
Ok((input_file_path, settings))
|
||||
|
|
Loading…
Reference in New Issue