Compare commits

...

3 Commits

Author SHA1 Message Date
scoobybejesus 9a28bfbf64 Script: checked for duplicate account numbers. 2020-11-27 12:57:12 -05:00
scoobybejesus 29f84a30d3 CSV Import: removed Options, cloning and unwrapping. 2020-11-27 12:10:50 -05:00
scoobybejesus 4faab11ed3 Error handling. Added user warning and exit()'s in place of asserts. Related formatting. 2020-11-27 11:50:45 -05:00
6 changed files with 86 additions and 48 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "cryptools" name = "cryptools"
version = "0.9.1" version = "0.9.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

@ -24,6 +24,7 @@
import csv import csv
import re import re
import os import os
import sys
unedited = "DigiTrnx.csv" # To be replaced with a launch arg, presumably unedited = "DigiTrnx.csv" # To be replaced with a launch arg, presumably
@ -47,6 +48,13 @@ with open(unedited) as fin, open(stage1, 'a') as fout:
wtr.writerow(header3) wtr.writerow(header3)
wtr.writerow(header4) wtr.writerow(header4)
# First, double check there are no account number duplicates
for i, val in enumerate(header):
if val != "":
if header.count(val) > 1:
print("### There is a duplicate account number (" + val +"). Please fix and re-run. ###")
sys.exit()
for row in rdr: for row in rdr:
if row[0] == "" or row[1] == "": if row[0] == "" or row[1] == "":
pass pass

View File

@ -242,6 +242,12 @@ pub(crate) fn create_lots_and_movements(
wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &chosen_home_currency, &raw_acct_map, &acct_map); wrap_mvmt_and_push(whole_mvmt, &ar, &lot, &chosen_home_currency, &raw_acct_map, &acct_map);
continue continue
} else { } else {
if acct.list_of_lots.borrow().len() == 0 {
println!("FATAL: There are zero lots to spend from in transaction:\n{:#?}",txn);
std::process::exit(1);
}
let list_of_lots_to_use = acct.list_of_lots.clone(); let list_of_lots_to_use = acct.list_of_lots.clone();
// the following returns vec to be iterated from beginning to end, which provides the index for the correct lot // the following returns vec to be iterated from beginning to end, which provides the index for the correct lot
@ -557,7 +563,8 @@ pub(crate) fn create_lots_and_movements(
} }
TxType::ToSelf => { TxType::ToSelf => {
if raw_acct.is_margin { if raw_acct.is_margin {
{ println!("\n Found margin actionrecord in toself txn # {} \n", txn.tx_number); use std::process::exit; exit(1) }; println!("FATAL: Consult developer. Found margin actionrecord in ToSelf transaction:\n{:#?}", txn);
std::process::exit(1);
} else { } else {
process_multiple_incoming_lots_and_mvmts( process_multiple_incoming_lots_and_mvmts(
txn_num, txn_num,
@ -729,7 +736,7 @@ fn fit_into_lots(
&chosen_home_currency, &chosen_home_currency,
&ar_map, &ar_map,
&raw_acct_map, &raw_acct_map,
&acct_map &acct_map,
); );
return; return;
} }
@ -777,6 +784,14 @@ fn fit_into_lots(
wrap_mvmt_and_push(mvmt_that_fits_in_lot, &spawning_ar, &lot, &chosen_home_currency, &raw_acct_map, &acct_map); wrap_mvmt_and_push(mvmt_that_fits_in_lot, &spawning_ar, &lot, &chosen_home_currency, &raw_acct_map, &acct_map);
let remainder_amt_to_recurse = remainder_amt + sum_of_mvmts_in_lot; let remainder_amt_to_recurse = remainder_amt + sum_of_mvmts_in_lot;
// println!("Remainder amount to recurse: {}", remainder_amt_to_recurse); // println!("Remainder amount to recurse: {}", remainder_amt_to_recurse);
if vec_of_ordered_index_values.len() == current_index_position + 1 {
println!("FATAL: Txn {} on {} spending {} {} has run out of lots to spend from.",
txn_num, lot.date_as_string, ar.amount, raw_acct.ticker);
println!("Account balance is only: {}", acct.get_sum_of_amts_in_lots());
std::process::exit(1);
}
current_index_position += 1; current_index_position += 1;
let lot_index = vec_of_ordered_index_values[current_index_position]; let lot_index = vec_of_ordered_index_values[current_index_position];
let newly_chosen_lot = list_of_lots_to_use.borrow()[lot_index].clone(); let newly_chosen_lot = list_of_lots_to_use.borrow()[lot_index].clone();
@ -806,7 +821,7 @@ fn fit_into_lots(
&chosen_home_currency, &chosen_home_currency,
&ar_map, &ar_map,
&raw_acct_map, &raw_acct_map,
&acct_map &acct_map,
); );
} }

View File

@ -51,67 +51,63 @@ fn import_accounts(
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
let header1 = rdr.headers()?.clone(); // account_num let header1 = rdr.headers()?.clone(); // account_num
let mut header2: Option<csv::StringRecord> = None; // name let mut header2: csv::StringRecord = csv::StringRecord::new(); // name
let mut header3: Option<csv::StringRecord> = None; // ticker let mut header3: csv::StringRecord = csv::StringRecord::new(); // ticker
let header4: csv::StringRecord; // is_margin let header4: csv::StringRecord; // is_margin
// A StringRecord doesn't accept the same range indexing we need below, so we create our own // Account Creation loop. With rdr.has_headers() set to true above, the first record here is the second row of the CSV
let mut headerstrings: Vec<String> = Vec::with_capacity(header1.len());
for element in header1.into_iter() {
headerstrings.push(element.to_string())
}
// Account Creation loop. We set hasheaders() to true above, so the first record here is the second row of the CSV
for result in rdr.records() { for result in rdr.records() {
// This initial iteration through records will break after the 4th row, after accounts have been created // This initial iteration through records will break after the 4th row, after accounts have been created
let record = result?; let record = result?;
if header2 == None { if header2.len() == 0 {
header2 = Some(record.clone()); header2 = record.clone();
continue // After header2 is set, continue to next record continue // After header2 is set, continue to next record
} }
else if header3 == None { else if header3.len() == 0 {
header3 = Some(record.clone()); header3 = record.clone();
continue // After header3 is set, continue to next record continue // After header3 is set, continue to next record
} }
else { else {
header4 = record.clone(); header4 = record.clone();
// println!("Assigned last header, record: {:?}", record); // println!("Assigned last header, record: {:?}", record);
let warn = "FATAL: Transactions will not import correctly if account numbers in the CSV import file aren't // A StringRecord doesn't accept the same range indexing needed below, so a Vec of Strings will be used
let mut headerstrings: Vec<String> = Vec::with_capacity(header1.len());
for field in header1.into_iter() {
headerstrings.push(field.to_string())
}
let acct_num_warn = "Transactions will not import correctly if account numbers in the CSV import file aren't
ordered chronologically (i.e., beginning in column 4 - the 1st account column - the value should be 1. ordered chronologically (i.e., beginning in column 4 - the 1st account column - the value should be 1.
The next column's value should be 2, then 3, etc, until the final account)."; The next column's value should be 2, then 3, etc, until the final account).";
// We've got all our header rows. It's now that we set up the accounts. // Header row variables have been set. It's now time to set up the accounts.
println!("Attempting to create accounts..."); println!("Attempting to create accounts...");
let mut no_dup_acct_nums = HashMap::new();
let length = &headerstrings.len(); let length = &headerstrings.len();
for num in headerstrings[3..*length].iter().enumerate() { for (idx, field) in headerstrings[3..*length].iter().enumerate() {
let counter = no_dup_acct_nums.entry(num).or_insert(0);
*counter += 1;
}
for acct_num in no_dup_acct_nums.keys() { // Parse account numbers.
assert_eq!(no_dup_acct_nums[acct_num], 1, "Found accounts with duplicate numbers during import."); let account_num = field.parse::<u16>().expect("Header row account number should parse into u16.");
} // For now, their columns aren't remembered. Instead, they must have a particular index. 0th idx is the 1st account, and so on.
if account_num != ((idx + 1) as u16) {
println!("FATAL: CSV Import: {}", acct_num_warn);
std::process::exit(1);
}
for (idx, item) in headerstrings[3..*length].iter().enumerate() {
// println!("Headerstrings value: {:?}", item);
let ind = idx+3; // Add three because the idx skips the first three 'key' columns let ind = idx+3; // Add three because the idx skips the first three 'key' columns
let account_num = item.parse::<u16>()?; let name:String = header2[ind].trim().to_string();
assert_eq!((idx + 1) as u16, account_num, "Found improper Account Number usage: {}", warn); let ticker:String = header3[ind].trim().to_string(); // no .to_uppercase() b/c margin...
let name:String = header2.clone().unwrap()[ind].trim().to_string();
let ticker:String = header3.clone().unwrap()[ind].trim().to_string(); // no .to_uppercase() b/c margin...
let margin_string = &header4.clone()[ind]; let margin_string = &header4.clone()[ind];
let is_margin:bool = match margin_string.trim().to_lowercase().as_str() { let is_margin:bool = match margin_string.trim().to_lowercase().as_str() {
"no" | "non" | "false" => false, "no" | "non" | "false" => false,
"yes" | "margin" | "true" => true, "yes" | "margin" | "true" => true,
_ => { println!("\n Couldn't parse margin value for acct {} {} \n",account_num, name); process::exit(1) } _ => {
println!("\n FATAL: CSV Import: Couldn't parse margin value for account {} {} \n",account_num, name);
process::exit(1)
}
}; };
let just_account: RawAccount = RawAccount { let just_account: RawAccount = RawAccount {

View File

@ -52,7 +52,8 @@ impl Transaction {
let ar2_ticker = ar2_ticker_comp[0]; let ar2_ticker = ar2_ticker_comp[0];
if first_ar.direction() == second_ar.direction() { if first_ar.direction() == second_ar.direction() {
println!("Program exiting. Found transaction with two actionRecords with the same polarity: {:?}", self); process::exit(1); println!("FATAL: TxType: Found transaction with two actionRecords with the same polarity: \n{:#?}", self);
process::exit(1);
} }
if ar1_ticker == ar2_ticker { if ar1_ticker == ar2_ticker {
if ar1_raw_acct.is_margin != ar2_raw_acct.is_margin { if ar1_raw_acct.is_margin != ar2_raw_acct.is_margin {
@ -67,10 +68,12 @@ impl Transaction {
} }
} }
else if self.action_record_idx_vec.len() > 2 { else if self.action_record_idx_vec.len() > 2 {
println!("Program exiting. Found transaction with too many actionRecords: {:?}", self); process::exit(1); println!("FATAL: TxType: Found transaction with too many actionRecords: \n{:#?}", self);
process::exit(1);
} }
else { else {
println!("Program exiting. Found transaction with no actionRecords: {:?}", self); process::exit(1); println!("FATAL: TxType: Found transaction with no actionRecords: \n{:#?}", self);
process::exit(1);
} }
} }
@ -92,11 +95,23 @@ impl Transaction {
} }
} else { } else {
assert_eq!(self.action_record_idx_vec.len(),2, if self.action_record_idx_vec.len() != 2 {
"Each txn can only have one or two ARs. Txn has {} ARs.", self.action_record_idx_vec.len()); println!("FATAL: Each transaction may have up to two actionrecords, and there are {} actionrecords in transaction:\n{:#?}",
self.action_record_idx_vec.len(), self);
}
let first_ar = ars.get(&self.action_record_idx_vec[0]).unwrap(); let first_ar = match ars.get(&self.action_record_idx_vec[0]) {
let second_ar = ars.get(&self.action_record_idx_vec[1]).unwrap(); Some(x) => x,
None => {
println!("FATAL: ActionRecord not found for: \n{:#?}", self);
process::exit(1)}
};
let second_ar = match ars.get(&self.action_record_idx_vec[1]) {
Some(x) => x,
None => {
println!("FATAL: ActionRecord not found for: \n{:#?}", self);
process::exit(1)}
};
let first_acct = acct_map.get(&first_ar.account_key).unwrap(); let first_acct = acct_map.get(&first_ar.account_key).unwrap();
let second_acct = acct_map.get(&second_ar.account_key).unwrap(); let second_acct = acct_map.get(&second_ar.account_key).unwrap();
@ -144,7 +159,8 @@ impl Transaction {
quote = second_acct_raw_key; quote = second_acct_raw_key;
Ok((base, quote)) Ok((base, quote))
} else { } else {
println!("{}", VariousErrors::MarginNoUnderbar); use std::process::exit; exit(1) println!("FATAL: {}", VariousErrors::MarginNoUnderbar);
std::process::exit(1);
} }
} }

View File

@ -94,7 +94,10 @@ pub (crate) fn run_setup(cmd_args: super::Cli, cfg: super::Cfg) -> Result<(PathB
"h" => { "-" } "h" => { "-" }
"s" => { "/" } "s" => { "/" }
"p" => { "." } "p" => { "." }
_ => { println!("\nFATAL: The date-separator arg requires either an 'h', an 's', or a 'p'.\n"); process::exit(1) } _ => {
println!("\nFATAL: ENV: The date-separator arg requires either an 'h', an 's', or a 'p'.\n");
process::exit(1)
}
}; };
let input_file_path = match cmd_args.file_to_import { let input_file_path = match cmd_args.file_to_import {