1
0
Fork 1

Fix tracking of RG with incomplete opening BC

This commit is contained in:
Sarah Jamie Lewis 2024-05-14 10:47:29 -07:00
parent 85477bd8a0
commit fae69cd353
Signed by: sarah
GPG Key ID: F27FD21A270837EF
6 changed files with 831 additions and 282 deletions

View File

@ -1,9 +1,9 @@
use crate::{CommoditiesPriceOracle};
use crate::books::TransactionReference;
use crate::CommoditiesPriceOracle;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::ops::Neg;
use rust_decimal::prelude::ToPrimitive;
use crate::books::TransactionReference;
#[derive(Clone)]
pub struct AssetCommodity {
@ -22,12 +22,19 @@ impl AssetCommodity {
pub struct AssetAccount {
name: String,
commodities: HashMap<String, Vec<AssetCommodity>>,
running_total: HashMap<String, Decimal>,
running_total: HashMap<String, Decimal>,
// txid, date, commodity, quantity, cost,
pub(crate) transactions: Vec<(TransactionReference, String, String, Decimal, Decimal, String)>,
opening: HashMap<String, Decimal>,
pub(crate) transactions: Vec<(
TransactionReference,
String,
String,
Decimal,
Decimal,
String,
)>,
opening: HashMap<String, Decimal>,
pub(crate) realized_gains: Decimal,
opening_date: String
opening_date: String,
}
impl AssetAccount {
@ -39,7 +46,7 @@ impl AssetAccount {
running_total: HashMap::new(),
transactions: vec![],
opening: HashMap::new(),
opening_date: opening_date.clone()
opening_date: opening_date.clone(),
}
}
@ -50,13 +57,17 @@ impl AssetAccount {
println!("[UG] Calculating Unrealized Gains for {}", self.name);
let mut unrealized_gain = Decimal::new(0, 0);
let historical_cost = commodities.iter().fold(Decimal::new(0,0), |accum, x| {
println!("[UG] Historial {} {}", x.cost_basis , x.quantity);
accum + (x.cost_basis * x.quantity)
let historical_cost = commodities.iter().fold(Decimal::new(0, 0), |accum, x| {
println!("[UG] Historial {} {}", x.cost_basis, x.quantity);
accum + (x.cost_basis * x.quantity)
});
let current_cost = commodities.iter().fold(Decimal::new(0,0), |accum, x| {
println!("[UG] Current {} {}", oracle.lookup(&x.name, date) , x.quantity);
let current_cost = commodities.iter().fold(Decimal::new(0, 0), |accum, x| {
println!(
"[UG] Current {} {}",
oracle.lookup(&x.name, date),
x.quantity
);
accum + (oracle.lookup(&x.name, date) * x.quantity)
});
@ -70,18 +81,20 @@ impl AssetAccount {
pub fn total_nominal(&self, oracle: &CommoditiesPriceOracle, date: &String) -> f64 {
let mut total = 0.0;
for (commodity_name, quantity) in self.running_total.iter() {
let mut value = if commodity_name != "CAD" && commodity_name != "$" && commodity_name != "StickerSheets" {
(oracle.lookup(commodity_name, date) * quantity.abs()).to_f64().unwrap()
} else if commodity_name == "CAD" || commodity_name == "$" {
quantity.to_f64().unwrap()
let mut value = if commodity_name != "CAD" && commodity_name != "$" && commodity_name != "StickerSheets" {
(oracle.lookup(commodity_name, date) * quantity.abs())
.to_f64()
.unwrap()
} else if commodity_name == "CAD" || commodity_name == "$" {
quantity.to_f64().unwrap()
} else {
0.0
0.0
};
if self.name.starts_with("Liability") {
// flip sign
value = value.neg()
}
total += value;
}
total
@ -90,26 +103,27 @@ impl AssetAccount {
pub fn opening_nominal(&self, oracle: &CommoditiesPriceOracle, date: &String) -> f64 {
let mut total = 0.0;
for (commodity_name, quantity) in self.opening.iter() {
let mut value = if commodity_name != "CAD" && commodity_name!= "$" && commodity_name != "StickerSheets" {
(oracle.lookup(commodity_name, date) * quantity.abs()).to_f64().unwrap()
let mut value = if commodity_name != "CAD" && commodity_name != "$" && commodity_name != "StickerSheets" {
(oracle.lookup(commodity_name, date) * quantity.abs())
.to_f64()
.unwrap()
} else if commodity_name == "CAD" || commodity_name == "$" {
quantity.to_f64().unwrap()
quantity.to_f64().unwrap()
} else {
0.0
};
if self.name.starts_with("Liability") {
// flip sign
value = value.neg()
}
total += value;
}
total
}
pub fn trade(&mut self, txid: TransactionReference, name: String, cost: Decimal, quantity: Decimal, date: &String, oracle: &mut CommoditiesPriceOracle) -> Vec<(String, Decimal, String, Decimal)>{
pub fn trade(&mut self, txid: TransactionReference, name: String, cost: Decimal, quantity: Decimal, date: &String, oracle: &mut CommoditiesPriceOracle) -> Vec<(String, Decimal, String, Decimal)> {
let cost_basis = cost;
let asset_commodity = AssetCommodity {
cost_basis,
@ -120,55 +134,69 @@ impl AssetAccount {
match self.commodities.contains_key(&name) {
false => {
self.commodities.insert(name.clone(), vec![]);
self.running_total.insert(name.clone(), Decimal::new(0,0));
if *date == self.opening_date {
println!("Opening Total: {} {} {}", quantity, name, cost);
self.opening.insert(name.clone(), quantity);
}
}
_ => {
self.running_total.insert(name.clone(), Decimal::new(0, 0));
if *date == self.opening_date {
println!("Opening Total: {} {} {}", quantity, name, cost);
self.opening.insert(name.clone(), quantity);
}
}
_ => {}
}
*self.running_total.get_mut(&name).unwrap() += quantity;
println!("Running Total: {} {} {} {}", quantity, name, cost, *self.running_total.get_mut(&name).unwrap() );
println!(
"Running Total: {} {} {} {}",
quantity,
name,
cost,
*self.running_total.get_mut(&name).unwrap()
);
if self.name.starts_with("Assets") == false {
self.transactions.push((txid, date.clone(), name.clone(), cost, quantity, self.name.clone()));
self.transactions.push((
txid,
date.clone(),
name.clone(),
cost,
quantity,
self.name.clone(),
));
let mut meta_transactions = vec![];
meta_transactions.push((self.name.clone(), quantity, name, cost*quantity));
meta_transactions.push((self.name.clone(), quantity, name, cost * quantity));
// we don't track cost-basis for liabilities
return meta_transactions;
}
match self.commodities.get_mut(&name) {
None => {
panic!();
panic!();
}
Some(current_commodities) => {
current_commodities.sort_by(|a,b| {
a.cost_basis.cmp(&b.cost_basis)
});
current_commodities.sort_by(|a, b| a.cost_basis.cmp(&b.cost_basis));
current_commodities.reverse();
let mut quantity_needed = quantity.abs();
let mut base_cost = Decimal::new(0,0);
let mut base_cost = Decimal::new(0, 0);
if name != "$" {
for commodity in current_commodities.iter_mut() {
if commodity.quantity.is_zero() {
continue
continue;
}
if commodity.quantity <= quantity_needed {
// use all of this commodity....
println!("Taking {} {} @@ {}", commodity.quantity, commodity.name, commodity.cost_basis);
println!(
"[REALIZED GAIN CALC] Taking {} {} @@ {}",
commodity.quantity, commodity.name, commodity.cost_basis
);
base_cost += commodity.quantity * commodity.cost_basis;
quantity_needed -= commodity.quantity;
commodity.quantity = Decimal::new(0, 0);
} else {
// only use what we need from this...and then we are done...
println!("Taking {} {} @@ {}", quantity_needed, commodity.name, commodity.cost_basis);
println!(
"Taking {} {} @@ {}",
quantity_needed, commodity.name, commodity.cost_basis
);
base_cost += quantity_needed * commodity.cost_basis;
commodity.quantity -= quantity_needed;
break;
@ -179,65 +207,149 @@ impl AssetAccount {
}
let mut oracle_price = oracle.lookup(&name, date);
let mut buying = false;
let arbitrage_difference = if oracle_price == Decimal::new(1,0) {
let arbitrage_difference = if oracle_price == Decimal::new(1, 0) {
buying = true;
println!("[WARNING] NO Price found for {} {} {}", name, date, cost_basis);
// oracle.insert(name.clone(), date.clone(), cost_basis);
Decimal::new(0,0)
println!(
"[WARNING] NO Price found for {} {} {}",
name, date, cost_basis
);
// oracle.insert(name.clone(), date.clone(), cost_basis);
Decimal::new(0, 0)
} else {
oracle_price - cost
};
if (base_cost - (cost * quantity.abs())).is_zero() == false {
println!("Realized Gains: {} {} {} {} {} {} {} {} {}", self.name, (cost * quantity.abs()) - base_cost, base_cost, cost * quantity.abs(), date, base_cost/quantity, cost, oracle_price, arbitrage_difference);
println!(
"[DEBUG] Realized Gains: {} {} {} {} {} {} {} {} {}",
self.name,
(cost * quantity.abs()) - base_cost,
base_cost,
cost * quantity.abs(),
date,
base_cost / quantity,
cost,
oracle_price,
arbitrage_difference
);
}
let arbitrage_expense = (arbitrage_difference * quantity).neg();
let mut actual_gain = ((cost * quantity.abs()) - base_cost) + arbitrage_expense;
let mut actual_gain = ((cost * quantity.abs()) - base_cost) + arbitrage_expense;
if buying {
actual_gain = Decimal::new(0,0);
actual_gain = Decimal::new(0, 0);
}
self.realized_gains += actual_gain;
println!("[DEBUG] {} {} oracle price: {} cost: {} quantity: {} ae: {} {} ", name, base_cost, oracle_price, cost, quantity, arbitrage_expense, actual_gain);
println!(
"[REALIZED GAIN CALC] Realized {} base: {} oracle price: {} cost: {} quantity: {} ae: {} {} ",
name, base_cost, oracle_price, cost, quantity, arbitrage_expense, actual_gain
);
self.transactions.push((txid, date.clone(), name.clone(), cost, quantity, self.name.clone()));
self.transactions.push((
txid,
date.clone(),
name.clone(),
cost,
quantity,
self.name.clone(),
));
let mut meta_transactions = vec![];
meta_transactions.push((self.name.clone(),cost*quantity, name.clone(), base_cost.neg() ));
meta_transactions.push((
self.name.clone(),
cost * quantity,
name.clone(),
base_cost.neg(),
));
if actual_gain.is_zero() == false {
if actual_gain.is_sign_positive() {
self.transactions.push((txid, date.clone(), format!("$"), Decimal::new(1, 0), actual_gain.neg(), format!("Income:RealizedGains")));
meta_transactions.push((format!("Income:RealizedGains"), actual_gain.neg(), format!("$"), actual_gain.neg() ))
self.transactions.push((
txid,
date.clone(),
format!("$"),
Decimal::new(1, 0),
actual_gain.neg(),
format!("Income:RealizedGains"),
));
meta_transactions.push((
format!("Income:RealizedGains"),
actual_gain.neg(),
format!("$"),
actual_gain.neg(),
))
} else if actual_gain.is_sign_negative() {
self.transactions.push((txid, date.clone(), format!("$"), Decimal::new(1, 0), actual_gain.neg(), format!("Expenses:RealizedLoss")));
meta_transactions.push((format!("Expenses:RealizedLoss"), actual_gain.neg(), format!("$"), actual_gain.neg() ))
self.transactions.push((
txid,
date.clone(),
format!("$"),
Decimal::new(1, 0),
actual_gain.neg(),
format!("Expenses:RealizedLoss"),
));
meta_transactions.push((
format!("Expenses:RealizedLoss"),
actual_gain.neg(),
format!("$"),
actual_gain.neg(),
))
}
}
if arbitrage_expense.is_zero() == false {
if arbitrage_expense.is_sign_negative() {
self.transactions.push((txid, date.clone(), format!("$"), Decimal::new(1, 0), arbitrage_expense, format!("Income:ExchangeArbitrage")));
meta_transactions.push((format!("Income:ExchangeArbitrage"), arbitrage_expense, format!("$") ,arbitrage_expense))
self.transactions.push((
txid,
date.clone(),
format!("$"),
Decimal::new(1, 0),
arbitrage_expense,
format!("Income:ExchangeArbitrage"),
));
meta_transactions.push((
format!("Income:ExchangeArbitrage"),
arbitrage_expense,
format!("$"),
arbitrage_expense,
))
} else if arbitrage_expense.is_sign_positive() {
self.transactions.push((txid, date.clone(), format!("$"), Decimal::new(1, 0), arbitrage_expense, format!("Expenses:BankingFees:ExchangeArbitrage")));
meta_transactions.push((format!("Expenses:BankingFees:ExchangeArbitrage"), arbitrage_expense, format!("$"),arbitrage_expense ))
self.transactions.push((
txid,
date.clone(),
format!("$"),
Decimal::new(1, 0),
arbitrage_expense,
format!("Expenses:BankingFees:ExchangeArbitrage"),
));
meta_transactions.push((
format!("Expenses:BankingFees:ExchangeArbitrage"),
arbitrage_expense,
format!("$"),
arbitrage_expense,
))
}
}
return meta_transactions;
} else {
println!("[UG] DEBUG ASSET TRADE {} {} {}", cost, quantity, name);
current_commodities.push(AssetCommodity{
current_commodities.push(AssetCommodity {
cost_basis: cost_basis * quantity,
quantity: quantity,
name: name.clone()
name: name.clone(),
});
let mut meta_transactions = vec![];
self.transactions.push((txid, date.clone(), name.clone(), cost, quantity, self.name.clone()));
meta_transactions.push((self.name.clone(), quantity, name.clone(), cost*quantity));
self.transactions.push((
txid,
date.clone(),
name.clone(),
cost,
quantity,
self.name.clone(),
));
meta_transactions.push((self.name.clone(), quantity, name.clone(), cost * quantity));
return meta_transactions;
}
}
}
}
}
@ -251,18 +363,34 @@ impl AssetAccount {
match self.commodities.get_mut(&name) {
None => {
self.commodities.insert(name.clone(), vec![asset_commodity]);
self.running_total.insert(name.clone(), Decimal::new(0,0));
self.running_total.insert(name.clone(), Decimal::new(0, 0));
if *date == self.opening_date {
println!("Opening: {} {} {}", quantity, name, cost);
self.opening.insert(name.clone(), quantity);
}
println!(
"[REALIZED GAIN CALC] Opening: {} {} {}",
quantity, name, cost
);
self.opening.insert(name.clone(), quantity);
}
}
Some(account) => account.push(asset_commodity),
}
self.transactions.push((txid, date.clone(), name.clone(), cost, quantity, self.name.clone()));
self.transactions.push((
txid,
date.clone(),
name.clone(),
cost,
quantity,
self.name.clone(),
));
*self.running_total.get_mut(&name).unwrap() += quantity;
println!("Running Total: {} {} {} {}", quantity, name, cost, *self.running_total.get_mut(&name).unwrap() );
println!(
"Running Total: {} {} {} {}",
quantity,
name,
cost,
*self.running_total.get_mut(&name).unwrap()
);
}
}

View File

@ -1,11 +1,11 @@
use crate::{AssetAccount, CommoditiesPriceOracle, ExternalAccount};
use chrono::NaiveDate;
use ledger_parser::{LedgerItem, Posting, PostingAmount, Transaction};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use std::collections::{HashMap, HashSet};
use std::fs::read_to_string;
use std::str::FromStr;
use chrono::NaiveDate;
use rust_decimal::prelude::ToPrimitive;
pub type TransactionReference = usize;
@ -13,9 +13,13 @@ pub struct Books {
pub(crate) asset_accounts: HashMap<String, AssetAccount>,
pub(crate) external_accounts: HashMap<String, ExternalAccount>,
pub(crate) commodities_oracle: CommoditiesPriceOracle,
pub general_ledger: Vec<(TransactionReference, String, Vec<(String, String, Decimal, Decimal)>)>,
pub general_ledger: Vec<(
TransactionReference,
String,
Vec<(String, String, Decimal, Decimal)>,
)>,
opening_date: String,
closing_date: String
closing_date: String,
}
impl Books {
@ -26,7 +30,7 @@ impl Books {
commodities_oracle: CommoditiesPriceOracle::new(),
general_ledger: vec![],
opening_date: opening_date.clone(),
closing_date: closing_date.clone()
closing_date: closing_date.clone(),
}
}
@ -41,7 +45,6 @@ impl Books {
let mut total = Decimal::new(0, 0);
let mut commodities = HashSet::new();
let mut general_ledger_entry = vec![];
if tx.date > NaiveDate::from_str(&to_date).unwrap() {
break;
@ -53,12 +56,10 @@ impl Books {
if a.amount.commodity != b.amount.commodity {
// handle trading
self.handle_commodities_exchange(id, &tx);
continue
continue;
}
}
_ => {
}
_ => {}
}
}
@ -75,7 +76,8 @@ impl Books {
total += amount.amount.quantity;
commodities.insert(amount.amount.commodity.name.clone());
self.update_account(0,
self.update_account(
0,
&mut general_ledger_entry,
id as TransactionReference,
&posting.account,
@ -88,14 +90,14 @@ impl Books {
}
match final_line {
Some(posting) => {
if commodities.len() != 1 {
panic!("Trades should be handled separately")
}
println!("{},{},{},{}", id, tx.date, posting.account, total);
let commodity: Vec<&String> = commodities.iter().collect();
self.update_account(0,
self.update_account(
0,
&mut general_ledger_entry,
id as TransactionReference,
&posting.account,
@ -124,7 +126,16 @@ impl Books {
}
}
pub fn update_account(&mut self, depth: usize, general_ledger_entry: &mut Vec<(String, String, Decimal, Decimal)>, tx_ref: TransactionReference, account_name: &String, tx_date: &String, commodity_name: &String, commodity_quantity: Decimal) {
pub fn update_account(
&mut self,
depth: usize,
general_ledger_entry: &mut Vec<(String, String, Decimal, Decimal)>,
tx_ref: TransactionReference,
account_name: &String,
tx_date: &String,
commodity_name: &String,
commodity_quantity: Decimal,
) {
if account_name.starts_with("Assets:") || account_name.starts_with("Liability:") {
match self.asset_accounts.contains_key(account_name) {
false => {
@ -136,29 +147,54 @@ impl Books {
_ => {}
}
let asset_account = self.asset_accounts.get_mut(account_name).unwrap();
let cost_basis = self.commodities_oracle.lookup(commodity_name, tx_date);
let cost_basis = self.commodities_oracle.lookup(commodity_name, tx_date);
if commodity_quantity.is_sign_positive() {
asset_account.deposit( tx_ref, commodity_name.clone(), cost_basis, commodity_quantity, tx_date, Decimal::new(1,0));
general_ledger_entry.push((account_name.clone(), commodity_name.clone(), commodity_quantity, commodity_quantity*cost_basis));
} else {
let extra_transactions = asset_account.trade(
tx_ref,
commodity_name.clone(),
cost_basis,
commodity_quantity,
tx_date,
&mut self.commodities_oracle
);
if commodity_quantity.is_sign_positive() {
asset_account.deposit(
tx_ref,
commodity_name.clone(),
cost_basis,
commodity_quantity,
tx_date,
cost_basis,
);
general_ledger_entry.push((
account_name.clone(),
commodity_name.clone(),
commodity_quantity,
commodity_quantity * cost_basis,
));
} else {
let extra_transactions = asset_account.trade(
tx_ref,
commodity_name.clone(),
cost_basis,
commodity_quantity,
tx_date,
&mut self.commodities_oracle,
);
for (eaccount_name, commodity_quantity, commodity, nominal_value) in extra_transactions {
if *account_name != eaccount_name && depth == 0 {
self.update_account(1, general_ledger_entry, tx_ref, &eaccount_name.clone(), tx_date, &commodity, nominal_value)
} else {
general_ledger_entry.push((eaccount_name.clone(), commodity, commodity_quantity, nominal_value));
}
for (eaccount_name, commodity_quantity, commodity, nominal_value) in extra_transactions {
if *account_name != eaccount_name && depth == 0 {
self.update_account(
1,
general_ledger_entry,
tx_ref,
&eaccount_name.clone(),
tx_date,
&commodity,
nominal_value,
)
} else {
general_ledger_entry.push((
eaccount_name.clone(),
commodity,
commodity_quantity,
nominal_value,
));
}
}
}
} else {
// This is an external account
match self.external_accounts.contains_key(account_name) {
@ -168,8 +204,7 @@ impl Books {
} else if account_name.starts_with("Expenses") {
ExternalAccount::new(account_name.clone(), false)
} else if account_name.starts_with("Correction") {
ExternalAccount::new(account_name.clone(), true)
ExternalAccount::new(account_name.clone(), true)
} else {
ExternalAccount::new(account_name.clone(), false)
};
@ -184,50 +219,104 @@ impl Books {
let cost_basis = self.commodities_oracle.lookup(commodity_name, tx_date);
if !external_account.income_account {
external_account.deposit(tx_ref, commodity_name.clone(), commodity_quantity, tx_date);
general_ledger_entry.push((account_name.clone(), commodity_name.clone(), commodity_quantity, cost_basis*commodity_quantity));
general_ledger_entry.push((
account_name.clone(),
commodity_name.clone(),
commodity_quantity,
cost_basis * commodity_quantity,
));
} else {
external_account.withdraw(tx_ref, commodity_name.clone(), commodity_quantity, tx_date);
general_ledger_entry.push((account_name.clone(), commodity_name.clone(), commodity_quantity, cost_basis*commodity_quantity));
general_ledger_entry.push((
account_name.clone(),
commodity_name.clone(),
commodity_quantity,
cost_basis * commodity_quantity,
));
}
}
}
fn handle_commodities_exchange(&mut self, id: usize, tx: &Transaction) {
let mut general_ledger_entry = vec![];
let a = tx.postings[0].amount.as_ref().unwrap();
let a = tx.postings[0].amount.as_ref().unwrap();
let b = tx.postings[1].amount.as_ref().unwrap();
let a_account = tx.postings[0].account.clone();
let b_account = tx.postings[1].account.clone();
if a.amount.quantity.is_sign_negative() {
// Trading a for b
let cost_basis = b.amount.quantity / a.amount.quantity.abs() ;
let cost_basis = b.amount.quantity / a.amount.quantity.abs();
// Insert this price into the Oracle as the Market Price
if (a.amount.commodity.name == "CAD") {
let trade_basis = a.amount.quantity.abs() / b.amount.quantity;
self.commodities_oracle.insert(b.amount.commodity.name.clone(), tx.date.to_string(), trade_basis);
let trade_basis = a.amount.quantity.abs() / b.amount.quantity;
self.commodities_oracle.insert(
b.amount.commodity.name.clone(),
tx.date.to_string(),
trade_basis,
);
}
println!("NOMVAL: {} {} {} {} {} ", cost_basis, a.amount.quantity, a.amount.commodity.name, b.amount.quantity, b.amount.commodity.name);
let extra_transactions = self.asset_accounts.get_mut(&a_account).unwrap().trade(id as TransactionReference,a.amount.commodity.name.clone(), cost_basis, a.amount.quantity, &tx.date.to_string(), &mut self.commodities_oracle);
for (eaccount_name, quantity, commodity, nominal_value ) in extra_transactions {
if eaccount_name == a_account {
general_ledger_entry.push((eaccount_name.clone(), commodity.clone(), quantity, nominal_value));
println!(
"NOMVAL: {} {} {} {} {} ",
cost_basis, a.amount.quantity, a.amount.commodity.name, b.amount.quantity, b.amount.commodity.name
);
let extra_transactions = self.asset_accounts.get_mut(&a_account).unwrap().trade(
id as TransactionReference,
a.amount.commodity.name.clone(),
cost_basis,
a.amount.quantity,
&tx.date.to_string(),
&mut self.commodities_oracle,
);
for (eaccount_name, quantity, commodity, nominal_value) in extra_transactions {
if eaccount_name == a_account {
general_ledger_entry.push((
eaccount_name.clone(),
commodity.clone(),
quantity,
nominal_value,
));
} else {
self.update_account(1, &mut general_ledger_entry, id as TransactionReference, &eaccount_name, &tx.date.to_string(), &format!("$"), nominal_value)
self.update_account(
1,
&mut general_ledger_entry,
id as TransactionReference,
&eaccount_name,
&tx.date.to_string(),
&format!("$"),
nominal_value,
)
}
}
if (a.amount.commodity.name == "CAD") {
self.asset_accounts.get_mut(&b_account).unwrap().deposit(id as TransactionReference, b.amount.commodity.name.clone(), Decimal::new(1,0), b.amount.quantity, &tx.date.to_string(), a.amount.quantity.abs() / b.amount.quantity);
} else {
self.asset_accounts.get_mut(&b_account).unwrap().deposit(id as TransactionReference, b.amount.commodity.name.clone(), Decimal::new(1,0), b.amount.quantity, &tx.date.to_string(), Decimal::new(1,0));
}
general_ledger_entry.push((b_account, b.amount.commodity.name.clone(), b.amount.quantity, Decimal::new(1,0) * b.amount.quantity,));
if (a.amount.commodity.name == "CAD") {
self.asset_accounts.get_mut(&b_account).unwrap().deposit(
id as TransactionReference,
b.amount.commodity.name.clone(),
Decimal::new(1, 0),
b.amount.quantity,
&tx.date.to_string(),
a.amount.quantity.abs() / b.amount.quantity,
);
} else {
self.asset_accounts.get_mut(&b_account).unwrap().deposit(
id as TransactionReference,
b.amount.commodity.name.clone(),
Decimal::new(1, 0),
b.amount.quantity,
&tx.date.to_string(),
Decimal::new(1, 0),
);
}
general_ledger_entry.push((
b_account,
b.amount.commodity.name.clone(),
b.amount.quantity,
Decimal::new(1, 0) * b.amount.quantity,
));
} else {
panic!("Trade order should have selling first {} {}",a,b);
panic!("Trade order should have selling first {} {}", a, b);
}
self.general_ledger.push((
@ -265,6 +354,6 @@ impl Books {
"Total Realized + Unrealized Gains: {}",
unrealized_gains + realized_gains
);
return (unrealized_gains).to_f64().unwrap()
return (unrealized_gains).to_f64().unwrap();
}
}

View File

@ -1,7 +1,7 @@
use crate::books::TransactionReference;
use rust_decimal::Decimal;
use rust_decimal::prelude::ToPrimitive;
use crate::CommoditiesPriceOracle;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
// For tracking income and expenses...
// All Transactions are one way.
@ -51,9 +51,11 @@ impl ExternalAccount {
let mut total = 0.0;
for (txid, date, commodity, quantity) in self.transactions.iter() {
if commodity != "CAD" && commodity != "$" && commodity != "StickerSheets" {
total += (oracle.lookup(commodity, date) * quantity.abs()).to_f64().unwrap();
} else if commodity == "CAD" || commodity == "$" {
total += quantity.abs().to_f64().unwrap()
total += (oracle.lookup(commodity, date) * quantity.abs())
.to_f64()
.unwrap();
} else if commodity == "CAD" || commodity == "$" {
total += quantity.abs().to_f64().unwrap()
}
}
total

View File

@ -10,7 +10,6 @@ pub mod external_account;
pub mod oracle;
pub mod spreadsheet;
use clap::Parser;
/// Search for a pattern in a file and display the lines that contain it.
@ -19,8 +18,7 @@ struct CommandLineParser {
/// The pattern to look for
book_file: String,
start_date: String,
stop_date: String
stop_date: String,
}
fn main() {

View File

@ -31,12 +31,12 @@ impl CommoditiesPriceOracle {
match self.commodities.get(commodity) {
None => {
println!("no price history data for commodity: {}", commodity);
return Decimal::new(1,0)
return Decimal::new(1, 0);
}
Some(price_history) => match price_history.get(date) {
None => {
println!("[WARNING] no date history for {} on {}", commodity, date);
return Decimal::new(0,0)
return Decimal::new(0, 0);
}
Some(price) => price.clone(),
},

View File

@ -1,17 +1,16 @@
use std::cmp::Ordering;
use std::str::FromStr;
use chrono::{NaiveDate, NaiveTime};
use rust_decimal::prelude::{ToPrimitive, Zero};
use spreadsheet_ods::{CellStyle, format, Length, Sheet, Value, WorkBook};
use crate::{AssetAccount, Books, ExternalAccount};
use chrono::{NaiveDate, NaiveTime};
use icu_locid::locale;
use rust_decimal::prelude::{ToPrimitive, Zero};
use rust_decimal::Decimal;
use spreadsheet_ods::style::units::TextAlign;
use spreadsheet_ods::Value::Currency;
use spreadsheet_ods::{format, CellStyle, Length, Sheet, Value, WorkBook};
use std::cmp::Ordering;
use std::str::FromStr;
pub fn export_to_spreadsheet(name: &str, books: &Books, opening_date: &String, close_date: &String) {
let mut wb =WorkBook::new(locale!("en_CA"));
pub fn export_to_spreadsheet(name: &str, books: &Books, opening_date: &String, close_date: &String) {
let mut wb = WorkBook::new(locale!("en_CA"));
let mut header_style = CellStyle::new_empty();
header_style.set_font_bold();
@ -31,103 +30,179 @@ pub fn export_to_spreadsheet(name: &str, books: &Books, opening_date: &String,
currency_pretty_print.set_value_format(&currency_style);
let currency_pretty_print_style = wb.add_cellstyle(currency_pretty_print);
// Create a Balance Statement
let mut balance_statement = Sheet::new(format!("BalanceSheet"));
balance_statement.set_styled_value(0, 0, Value::Text(format!("Balance Sheet")), &header_style_ref);
balance_statement.set_styled_value(
0,
0,
Value::Text(format!("Balance Sheet")),
&header_style_ref,
);
balance_statement.set_col_width(0, Length::Pc(25.0));
balance_statement.set_col_width(1, Length::Pc(25.0));
balance_statement.set_col_width(2, Length::Pc(25.0));
balance_statement.set_col_width(3, Length::Pc(25.0));
balance_statement.set_col_width(4, Length::Pc(25.0));
// We first need a list of all of our assets and liabilities
let mut asset_accounts : Vec<(String, AssetAccount)> = books.asset_accounts.iter().map(|(a,b)| (a.clone(), b.clone())).collect();
asset_accounts.sort_by(|a,b| {
let mut asset_accounts: Vec<(String, AssetAccount)> = books
.asset_accounts
.iter()
.map(|(a, b)| (a.clone(), b.clone()))
.collect();
asset_accounts.sort_by(|a, b| {
if a.0.starts_with("Asset") && b.0.starts_with("Liability") {
return Ordering::Less
} if a.0.starts_with("Liability") && b.0.starts_with("Asset") {
return Ordering::Greater
return Ordering::Less;
}
if a.0.starts_with("Liability") && b.0.starts_with("Asset") {
return Ordering::Greater;
} else {
return b.1.total_nominal(&books.commodities_oracle, close_date).total_cmp(&a.1.total_nominal(&books.commodities_oracle, close_date))
return b
.1
.total_nominal(&books.commodities_oracle, close_date)
.total_cmp(&a.1.total_nominal(&books.commodities_oracle, close_date));
}
});
// Assets Section
balance_statement.set_styled_value(2, 0, Value::Text(format!("Assets")), &header_style_ref);
balance_statement.set_styled_value(2, 2, Value::Text(format!("Closing Value")), &header_style_ref);
balance_statement.set_styled_value(2, 3, Value::Text(format!("Opening Value")), &header_style_ref);
balance_statement.set_styled_value(
2,
2,
Value::Text(format!("Closing Value")),
&header_style_ref,
);
balance_statement.set_styled_value(
2,
3,
Value::Text(format!("Opening Value")),
&header_style_ref,
);
let mut row_index = 3;
let start = row_index;
for (account_name, account) in asset_accounts.iter() {
if account_name.starts_with("Asset") {
let total_nominal = account.total_nominal(&books.commodities_oracle, close_date);
let total_opening = account.opening_nominal(&books.commodities_oracle, opening_date);
balance_statement.set_styled_value(row_index, 1, Value::Text(account_name.clone().replace("Assets:", "")), &general_text_style);
balance_statement.set_styled_value(row_index, 2, Value::Currency(total_nominal, format!("CAD")), &currency_pretty_print_style);
balance_statement.set_styled_value(row_index, 3, Value::Currency(total_opening, format!("CAD")), &currency_pretty_print_style);
balance_statement.set_styled_value(
row_index,
1,
Value::Text(account_name.clone().replace("Assets:", "")),
&general_text_style,
);
balance_statement.set_styled_value(
row_index,
2,
Value::Currency(total_nominal, format!("CAD")),
&currency_pretty_print_style,
);
balance_statement.set_styled_value(
row_index,
3,
Value::Currency(total_opening, format!("CAD")),
&currency_pretty_print_style,
);
row_index+=1;
row_index += 1;
}
}
balance_statement.set_styled_value(row_index, 0, format!("Total"), &header_style_ref);
balance_statement.set_formula(row_index, 2, format!("SUM(C{}:C{})", start+1, row_index));
balance_statement.set_cellstyle(row_index,2, &currency_pretty_print_style);
balance_statement.set_formula(row_index, 3, format!("SUM(D{}:D{})", start+1, row_index));
balance_statement.set_cellstyle(row_index,3, &currency_pretty_print_style);
balance_statement.set_formula(row_index, 2, format!("SUM(C{}:C{})", start + 1, row_index));
balance_statement.set_cellstyle(row_index, 2, &currency_pretty_print_style);
balance_statement.set_formula(row_index, 3, format!("SUM(D{}:D{})", start + 1, row_index));
balance_statement.set_cellstyle(row_index, 3, &currency_pretty_print_style);
let mut assets_total_row = row_index;
row_index+=2;
row_index += 2;
balance_statement.set_styled_value(row_index, 0, Value::Text(format!("Liabilities")), &header_style_ref);
balance_statement.set_styled_value(
row_index,
0,
Value::Text(format!("Liabilities")),
&header_style_ref,
);
let start = row_index;
for (account_name, account) in asset_accounts.iter() {
if account_name.starts_with("Liability") {
let total_nominal = account.total_nominal(&books.commodities_oracle, close_date);
let total_opening = account.opening_nominal(&books.commodities_oracle, opening_date);
if total_opening.is_zero() && total_nominal.is_zero() {
continue
continue;
}
balance_statement.set_styled_value(row_index, 1, Value::Text(account_name.clone().replace("Liability:", "")), &general_text_style);
balance_statement.set_styled_value(row_index, 2, Value::Currency(total_nominal, format!("CAD")), &currency_pretty_print_style);
balance_statement.set_styled_value(row_index, 3, Value::Currency(total_opening, format!("CAD")), &currency_pretty_print_style);
balance_statement.set_styled_value(
row_index,
1,
Value::Text(account_name.clone().replace("Liability:", "")),
&general_text_style,
);
balance_statement.set_styled_value(
row_index,
2,
Value::Currency(total_nominal, format!("CAD")),
&currency_pretty_print_style,
);
balance_statement.set_styled_value(
row_index,
3,
Value::Currency(total_opening, format!("CAD")),
&currency_pretty_print_style,
);
row_index+=1;
row_index += 1;
}
}
balance_statement.set_styled_value(row_index, 0, format!("Total"), &header_style_ref);
balance_statement.set_formula(row_index, 2, format!("SUM(C{}:C{})", start+1, row_index));
balance_statement.set_cellstyle(row_index,2, &currency_pretty_print_style);
balance_statement.set_formula(row_index, 3, format!("SUM(D{}:D{})", start+1, row_index));
balance_statement.set_cellstyle(row_index,3, &currency_pretty_print_style);
balance_statement.set_formula(row_index, 2, format!("SUM(C{}:C{})", start + 1, row_index));
balance_statement.set_cellstyle(row_index, 2, &currency_pretty_print_style);
balance_statement.set_formula(row_index, 3, format!("SUM(D{}:D{})", start + 1, row_index));
balance_statement.set_cellstyle(row_index, 3, &currency_pretty_print_style);
let mut liability_total_row = row_index;
row_index+=2;
row_index += 2;
balance_statement.set_styled_value(row_index, 0, format!("Net Assets"), &header_style_ref);
balance_statement.set_formula(row_index, 2, format!("C{} - C{}", assets_total_row+1, liability_total_row+1));
balance_statement.set_cellstyle(row_index,2, &currency_pretty_print_style);
balance_statement.set_formula(
row_index,
2,
format!("C{} - C{}", assets_total_row + 1, liability_total_row + 1),
);
balance_statement.set_cellstyle(row_index, 2, &currency_pretty_print_style);
balance_statement.set_formula(row_index, 3, format!("D{} - D{}", assets_total_row+1, liability_total_row+1));
balance_statement.set_cellstyle(row_index,3, &currency_pretty_print_style);
balance_statement.set_formula(
row_index,
3,
format!("D{} - D{}", assets_total_row + 1, liability_total_row + 1),
);
balance_statement.set_cellstyle(row_index, 3, &currency_pretty_print_style);
let balance_sheet_opening_assets_row = row_index;
let balance_sheet_total_net_assets_row = row_index;
// Create an Income Statement
let mut income_statement = Sheet::new(format!("IncomeStatement"));
income_statement.set_styled_value(0, 0, Value::Text(format!("Income Statement")), &header_style_ref);
income_statement.set_styled_value(
0,
0,
Value::Text(format!("Income Statement")),
&header_style_ref,
);
income_statement.set_col_width(0, Length::Pc(25.0));
income_statement.set_col_width(1, Length::Pc(25.0));
// We first need a list of all of our incomes and expenses, ordered by value...
let mut external_accounts : Vec<(String, ExternalAccount)> = books.external_accounts.iter().map(|(a,b)| (a.clone(), b.clone())).collect();
external_accounts.sort_by(|a,b| {
let mut external_accounts: Vec<(String, ExternalAccount)> = books
.external_accounts
.iter()
.map(|(a, b)| (a.clone(), b.clone()))
.collect();
external_accounts.sort_by(|a, b| {
if a.0.starts_with("Income") && b.0.starts_with("Expense") {
return Ordering::Less
} if a.0.starts_with("Expense") && b.0.starts_with("Income") {
return Ordering::Greater
return Ordering::Less;
}
if a.0.starts_with("Expense") && b.0.starts_with("Income") {
return Ordering::Greater;
} else {
return a.1.total_nominal(&books.commodities_oracle).total_cmp(&b.1.total_nominal(&books.commodities_oracle))
return a
.1
.total_nominal(&books.commodities_oracle)
.total_cmp(&b.1.total_nominal(&books.commodities_oracle));
}
});
@ -137,203 +212,460 @@ pub fn export_to_spreadsheet(name: &str, books: &Books, opening_date: &String,
let start = row_index;
for (account_name, account) in external_accounts.iter() {
if account_name.starts_with("Income") {
income_statement.set_styled_value(row_index, 0, Value::Text(account_name.clone().replace("Income:", "")), &general_text_style);
income_statement.set_styled_value(row_index, 1, Value::Currency(account.total_nominal(&books.commodities_oracle), format!("CAD")), &currency_pretty_print_style);
row_index+=1;
income_statement.set_styled_value(
row_index,
0,
Value::Text(account_name.clone().replace("Income:", "")),
&general_text_style,
);
income_statement.set_styled_value(
row_index,
1,
Value::Currency(
account.total_nominal(&books.commodities_oracle),
format!("CAD"),
),
&currency_pretty_print_style,
);
row_index += 1;
}
}
income_statement.set_styled_value(row_index, 0, format!("Total"), &header_style_ref);
income_statement.set_formula(row_index, 1, format!("SUM(B{}:B{})", start+1, row_index));
income_statement.set_cellstyle(row_index,1, &currency_pretty_print_style);
income_statement.set_formula(row_index, 1, format!("SUM(B{}:B{})", start + 1, row_index));
income_statement.set_cellstyle(row_index, 1, &currency_pretty_print_style);
let mut revenue_total_row = row_index;
row_index+=2;
row_index += 2;
// Now to Expenses...
income_statement.set_styled_value(row_index, 0, Value::Text(format!("Expenses")), &general_text_style);
row_index+=1;
income_statement.set_styled_value(
row_index,
0,
Value::Text(format!("Expenses")),
&general_text_style,
);
row_index += 1;
let start = row_index;
for (account_name, account) in external_accounts.iter() {
if account_name.starts_with("Expense") {
income_statement.set_styled_value(row_index, 0, Value::Text(account_name.clone().replace("Expenses:", "")), &general_text_style);
income_statement.set_styled_value(row_index, 1, Value::Currency(account.total_nominal(&books.commodities_oracle), format!("CAD")), &currency_pretty_print_style);
row_index+=1;
income_statement.set_styled_value(
row_index,
0,
Value::Text(account_name.clone().replace("Expenses:", "")),
&general_text_style,
);
income_statement.set_styled_value(
row_index,
1,
Value::Currency(
account.total_nominal(&books.commodities_oracle),
format!("CAD"),
),
&currency_pretty_print_style,
);
row_index += 1;
}
}
income_statement.set_styled_value(row_index, 0, format!("Total"), &header_style_ref);
income_statement.set_formula(row_index, 1, format!("SUM(B{}:B{})", start+1, row_index));
income_statement.set_cellstyle(row_index,1, &currency_pretty_print_style);
income_statement.set_formula(row_index, 1, format!("SUM(B{}:B{})", start + 1, row_index));
income_statement.set_cellstyle(row_index, 1, &currency_pretty_print_style);
let mut expense_total_row = row_index;
row_index+=2;
row_index += 2;
income_statement.set_styled_value(row_index, 0, format!("Net Income"), &header_style_ref);
income_statement.set_formula(row_index, 1, format!("B{} - B{}", revenue_total_row+1, expense_total_row+1));
income_statement.set_cellstyle(row_index,1, &currency_pretty_print_style);
income_statement.set_formula(
row_index,
1,
format!("B{} - B{}", revenue_total_row + 1, expense_total_row + 1),
);
income_statement.set_cellstyle(row_index, 1, &currency_pretty_print_style);
let balance_sheet_summary_row = balance_sheet_total_net_assets_row + 2;
balance_statement.set_styled_value(balance_sheet_summary_row,0, format!("Income statement balance for the year"), &header_style_ref);
balance_statement.set_formula(balance_sheet_summary_row,1, format!("IncomeStatement.B{}", row_index+1));
balance_statement.set_styled_value(
balance_sheet_summary_row,
0,
format!("Income statement balance for the year"),
&header_style_ref,
);
balance_statement.set_formula(
balance_sheet_summary_row,
1,
format!("IncomeStatement.B{}", row_index + 1),
);
balance_statement.set_styled_value(balance_sheet_summary_row+1,0, format!("Opening Assets + Income"), &header_style_ref);
balance_statement.set_formula(balance_sheet_summary_row+1,1, format!("B{}+D{}", balance_sheet_summary_row+1,balance_sheet_opening_assets_row+1));
balance_statement.set_styled_value(
balance_sheet_summary_row + 1,
0,
format!("Opening Assets + Income"),
&header_style_ref,
);
balance_statement.set_formula(
balance_sheet_summary_row + 1,
1,
format!(
"B{}+D{}",
balance_sheet_summary_row + 1,
balance_sheet_opening_assets_row + 1
),
);
let exchange_gains = books.calculate_gains(close_date);
balance_statement.set_styled_value(balance_sheet_summary_row+2,0, format!("Unrealized Gains/Losses (Assets)"), &header_style_ref);
balance_statement.set_styled_value(balance_sheet_summary_row+2,1, Currency(exchange_gains, format!("CAD")), &currency_pretty_print_style);
balance_statement.set_styled_value(balance_sheet_summary_row+3,0, format!("Opening Assets + Income + Unrealized Gains/Losses (Assets)"), &header_style_ref);
balance_statement.set_formula(balance_sheet_summary_row+3,1, format!("B{}+B{}", balance_sheet_summary_row+2,balance_sheet_summary_row+3));
balance_statement.set_styled_value(
balance_sheet_summary_row + 2,
0,
format!("Unrealized Gains/Losses (Assets)"),
&header_style_ref,
);
balance_statement.set_styled_value(
balance_sheet_summary_row + 2,
1,
Currency(exchange_gains, format!("CAD")),
&currency_pretty_print_style,
);
balance_statement.set_styled_value(
balance_sheet_summary_row + 3,
0,
format!("Opening Assets + Income + Unrealized Gains/Losses (Assets)"),
&header_style_ref,
);
balance_statement.set_formula(
balance_sheet_summary_row + 3,
1,
format!(
"B{}+B{}",
balance_sheet_summary_row + 2,
balance_sheet_summary_row + 3
),
);
wb.push_sheet(balance_statement);
wb.push_sheet(income_statement);
// Create a General Ledger
let mut general_ledger = Sheet::new(format!("GeneralLedger"));
general_ledger.set_styled_value(0, 0, Value::Text(format!("General Ledger")), &header_style_ref);
general_ledger.set_styled_value(
0,
0,
Value::Text(format!("General Ledger")),
&header_style_ref,
);
let start_row = 1;
general_ledger.set_styled_value(start_row, 0, Value::Text(String::from("Ref")), &header_style_ref);
general_ledger.set_styled_value(
start_row,
0,
Value::Text(String::from("Ref")),
&header_style_ref,
);
general_ledger.set_col_width(0, Length::Pc(15.0));
general_ledger.set_styled_value(start_row, 1, Value::Text(String::from("Date")), &header_style_ref);
general_ledger.set_styled_value(
start_row,
1,
Value::Text(String::from("Date")),
&header_style_ref,
);
general_ledger.set_col_width(1, Length::Pc(15.0));
general_ledger.set_styled_value(start_row, 2, Value::Text(String::from("Account")), &header_style_ref);
general_ledger.set_styled_value(
start_row,
2,
Value::Text(String::from("Account")),
&header_style_ref,
);
general_ledger.set_col_width(2, Length::Pc(30.0));
general_ledger.set_styled_value(start_row, 3, Value::Text(String::from("Recorded CAD Value")), &header_style_ref);
general_ledger.set_styled_value(
start_row,
3,
Value::Text(String::from("Recorded CAD Value")),
&header_style_ref,
);
general_ledger.set_col_width(3, Length::Pc(15.0));
general_ledger.set_styled_value(start_row, 4, Value::Text(String::from("Recorded Commodity Value")), &header_style_ref);
general_ledger.set_styled_value(
start_row,
4,
Value::Text(String::from("Recorded Commodity Value")),
&header_style_ref,
);
general_ledger.set_col_width(4, Length::Pc(15.0));
general_ledger.set_styled_value(start_row, 5, Value::Text(String::from("Commodity")), &header_style_ref);
general_ledger.set_styled_value(
start_row,
5,
Value::Text(String::from("Commodity")),
&header_style_ref,
);
general_ledger.set_col_width(5, Length::Pc(15.0));
general_ledger.set_header_rows(start_row,1);
general_ledger.set_header_rows(start_row, 1);
let mut row_index = 2;
// Create a sheet for each tracked account
let mut running_total = Decimal::new(0,0);
let mut running_total = Decimal::new(0, 0);
for (txid, date, postings) in books.general_ledger.iter() {
general_ledger.set_styled_value(row_index, 0, Value::Number(*txid as f64), &header_style_ref);
let recorded_date = NaiveDate::from_str(date).unwrap();
general_ledger.set_styled_value(row_index, 1, Value::DateTime(recorded_date.and_time(NaiveTime::default())), &header_style_ref);
row_index +=1;
for(account, commodity, quantity, nominal_value) in postings.iter() {
general_ledger.set_styled_value(
row_index,
1,
Value::DateTime(recorded_date.and_time(NaiveTime::default())),
&header_style_ref,
);
row_index += 1;
for (account, commodity, quantity, nominal_value) in postings.iter() {
running_total += nominal_value;
general_ledger.set_styled_value(row_index, 2, Value::Text(account.clone()), &general_text_style);
general_ledger.set_styled_value(row_index, 3, Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")), &currency_pretty_print_style);
general_ledger.set_styled_value(row_index, 4, Value::Currency(quantity.to_f64().unwrap(), commodity.clone()),&general_text_style);
general_ledger.set_styled_value(row_index, 5, Value::Text(commodity.clone().replace("$", "CAD")), &general_text_style);
row_index +=1;
general_ledger.set_styled_value(
row_index,
2,
Value::Text(account.clone()),
&general_text_style,
);
general_ledger.set_styled_value(
row_index,
3,
Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")),
&currency_pretty_print_style,
);
general_ledger.set_styled_value(
row_index,
4,
Value::Currency(quantity.to_f64().unwrap(), commodity.clone()),
&general_text_style,
);
general_ledger.set_styled_value(
row_index,
5,
Value::Text(commodity.clone().replace("$", "CAD")),
&general_text_style,
);
row_index += 1;
}
general_ledger.set_styled_value(row_index, 3, Value::Currency(running_total.to_f64().unwrap(), String::from("CAD")), &currency_pretty_print_style);
row_index+=2;
general_ledger.set_styled_value(
row_index,
3,
Value::Currency(running_total.to_f64().unwrap(), String::from("CAD")),
&currency_pretty_print_style,
);
row_index += 2;
}
wb.push_sheet(general_ledger);
// Create a sheet for each asset account
// We first need a list of all of our incomes and expenses, ordered by value...
let mut asset_accounts : Vec<(String, AssetAccount)> = books.asset_accounts.iter().map(|(a,b)| (a.clone(), b.clone())).collect();
asset_accounts.sort_by(|a,b| {
let mut asset_accounts: Vec<(String, AssetAccount)> = books
.asset_accounts
.iter()
.map(|(a, b)| (a.clone(), b.clone()))
.collect();
asset_accounts.sort_by(|a, b| {
if a.0.starts_with("Assets") && b.0.starts_with("Liability") {
return Ordering::Less
} if a.0.starts_with("Liability") && b.0.starts_with("Assets") {
return Ordering::Greater
return Ordering::Less;
}
if a.0.starts_with("Liability") && b.0.starts_with("Assets") {
return Ordering::Greater;
} else {
return b.1.total_nominal(&books.commodities_oracle, close_date).total_cmp(&a.1.total_nominal(&books.commodities_oracle, close_date))
return b
.1
.total_nominal(&books.commodities_oracle, close_date)
.total_cmp(&a.1.total_nominal(&books.commodities_oracle, close_date));
}
});
for (external_account, account) in asset_accounts.iter() {
let mut sheet = Sheet::new(format!("{}", external_account.to_lowercase().replace(":", "")));
sheet.set_styled_value(0, 0, Value::Text(format!("Account: {}", external_account)), &header_style_ref);
let mut sheet = Sheet::new(format!(
"{}",
external_account.to_lowercase().replace(":", "")
));
sheet.set_styled_value(
0,
0,
Value::Text(format!("Account: {}", external_account)),
&header_style_ref,
);
let start_row = 1;
sheet.set_styled_value(start_row, 0, Value::Text(String::from("Ref")), &header_style_ref);
sheet.set_styled_value(
start_row,
0,
Value::Text(String::from("Ref")),
&header_style_ref,
);
sheet.set_col_width(0, Length::Pc(15.0));
sheet.set_styled_value(start_row, 1, Value::Text(String::from("Date")), &header_style_ref);
sheet.set_styled_value(
start_row,
1,
Value::Text(String::from("Date")),
&header_style_ref,
);
sheet.set_col_width(1, Length::Pc(15.0));
sheet.set_styled_value(start_row, 2, Value::Text(String::from("Recorded CAD Value")), &header_style_ref);
sheet.set_styled_value(
start_row,
2,
Value::Text(String::from("Recorded CAD Value")),
&header_style_ref,
);
sheet.set_col_width(2, Length::Pc(15.0));
sheet.set_styled_value(start_row, 3, Value::Text(String::from("Recorded Commodity Value")), &header_style_ref);
sheet.set_styled_value(
start_row,
3,
Value::Text(String::from("Recorded Commodity Value")),
&header_style_ref,
);
sheet.set_col_width(3, Length::Pc(15.0));
sheet.set_styled_value(start_row, 4, Value::Text(String::from("Commodity")), &header_style_ref);
sheet.set_styled_value(
start_row,
4,
Value::Text(String::from("Commodity")),
&header_style_ref,
);
sheet.set_col_width(4, Length::Pc(15.0));
sheet.set_header_rows(start_row,1);
sheet.set_header_rows(start_row, 1);
let start_row = 2;
for(row, (txref, date, commodity, cost, quantity, booked_account)) in account.transactions.iter().enumerate() {
let index = start_row+(row as u32);
for (row, (txref, date, commodity, cost, quantity, booked_account)) in account.transactions.iter().enumerate() {
let index = start_row + (row as u32);
let recorded_date = NaiveDate::from_str(date).unwrap();
let nominal_value = if commodity != "CAD" && commodity != "$" && commodity != "StickerSheets" {
books.commodities_oracle.lookup(commodity, date) * quantity
} else {
quantity * Decimal::new(1,0)
quantity * Decimal::new(1, 0)
};
sheet.set_styled_value(index, 0, Value::Number(*txref as f64), &header_style_ref);
sheet.set_styled_value(index, 1, Value::DateTime(recorded_date.and_time(NaiveTime::default())), &general_text_style);
sheet.set_styled_value(index, 2, Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")), &currency_pretty_print_style);
sheet.set_styled_value(index, 3, Value::Currency(quantity.to_f64().unwrap(), commodity.clone()), &general_text_style);
sheet.set_styled_value(index, 4, Value::Text(commodity.clone().replace("$", "CAD")), &general_text_style);
sheet.set_styled_value(index, 5, Value::Text(booked_account.clone()), &general_text_style);
sheet.set_styled_value(
index,
1,
Value::DateTime(recorded_date.and_time(NaiveTime::default())),
&general_text_style,
);
sheet.set_styled_value(
index,
2,
Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")),
&currency_pretty_print_style,
);
sheet.set_styled_value(
index,
3,
Value::Currency(quantity.to_f64().unwrap(), commodity.clone()),
&general_text_style,
);
sheet.set_styled_value(
index,
4,
Value::Text(commodity.clone().replace("$", "CAD")),
&general_text_style,
);
sheet.set_styled_value(
index,
5,
Value::Text(booked_account.clone()),
&general_text_style,
);
}
wb.push_sheet(sheet);
}
// Create a sheet for each tracked account
for (external_account, account) in external_accounts.iter() {
let mut sheet = Sheet::new(format!("{}", external_account.to_lowercase().replace(":", "")));
sheet.set_styled_value(0, 0, Value::Text(format!("Account: {}", external_account)), &header_style_ref);
let mut sheet = Sheet::new(format!(
"{}",
external_account.to_lowercase().replace(":", "")
));
sheet.set_styled_value(
0,
0,
Value::Text(format!("Account: {}", external_account)),
&header_style_ref,
);
let mut start_row = 1;
sheet.set_styled_value(start_row, 0, Value::Text(String::from("Ref")), &header_style_ref);
sheet.set_styled_value(
start_row,
0,
Value::Text(String::from("Ref")),
&header_style_ref,
);
sheet.set_col_width(0, Length::Pc(15.0));
sheet.set_styled_value(start_row, 1, Value::Text(String::from("Date")), &header_style_ref);
sheet.set_styled_value(
start_row,
1,
Value::Text(String::from("Date")),
&header_style_ref,
);
sheet.set_col_width(1, Length::Pc(15.0));
sheet.set_styled_value(start_row, 2, Value::Text(String::from("Recorded CAD Value")), &header_style_ref);
sheet.set_styled_value(
start_row,
2,
Value::Text(String::from("Recorded CAD Value")),
&header_style_ref,
);
sheet.set_col_width(2, Length::Pc(15.0));
sheet.set_styled_value(start_row, 3, Value::Text(String::from("Recorded Commodity Value")), &header_style_ref);
sheet.set_styled_value(
start_row,
3,
Value::Text(String::from("Recorded Commodity Value")),
&header_style_ref,
);
sheet.set_col_width(3, Length::Pc(15.0));
sheet.set_styled_value(start_row, 4, Value::Text(String::from("Commodity")), &header_style_ref);
sheet.set_styled_value(
start_row,
4,
Value::Text(String::from("Commodity")),
&header_style_ref,
);
sheet.set_col_width(4, Length::Pc(15.0));
sheet.set_header_rows(start_row,1);
sheet.set_header_rows(start_row, 1);
start_row += 1;
for(row, (txref, date, commodity, quantity)) in account.transactions.iter().enumerate() {
let index = start_row+(row as u32);
for (row, (txref, date, commodity, quantity)) in account.transactions.iter().enumerate() {
let index = start_row + (row as u32);
let recorded_date = NaiveDate::from_str(date).unwrap();
let nominal_value = if commodity != "CAD" && commodity != "$" && commodity != "StickerSheets" {
let nominal_value = if commodity != "CAD" && commodity != "$" && commodity != "StickerSheets" {
books.commodities_oracle.lookup(commodity, date) * quantity
} else {
Decimal::new(1,0 ) * quantity
Decimal::new(1, 0) * quantity
};
sheet.set_styled_value(index, 0, Value::Number(*txref as f64), &header_style_ref);
sheet.set_styled_value(index, 1, Value::DateTime(recorded_date.and_time(NaiveTime::default())), &general_text_style);
sheet.set_styled_value(index, 2, Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")), &currency_pretty_print_style);
sheet.set_styled_value(index, 3, Value::Currency(quantity.to_f64().unwrap(), commodity.clone()), &general_text_style);
sheet.set_styled_value(index, 4, Value::Text(commodity.clone().replace("$", "CAD")), &general_text_style);
sheet.set_styled_value(
index,
1,
Value::DateTime(recorded_date.and_time(NaiveTime::default())),
&general_text_style,
);
sheet.set_styled_value(
index,
2,
Value::Currency(nominal_value.to_f64().unwrap(), String::from("CAD")),
&currency_pretty_print_style,
);
sheet.set_styled_value(
index,
3,
Value::Currency(quantity.to_f64().unwrap(), commodity.clone()),
&general_text_style,
);
sheet.set_styled_value(
index,
4,
Value::Text(commodity.clone().replace("$", "CAD")),
&general_text_style,
);
}
wb.push_sheet(sheet);
}
spreadsheet_ods::write_ods(&mut wb, name);
}
}