use crate::prices::{Prices, PricesError};
use crate::{Amount, Commodity, CommodityPosition, Ledger, Posting, Reality, Transaction};
use rust_decimal::RoundingStrategy;
pub fn handle_foreign_currencies<F1, F2, F3>(
ledger: &mut Ledger,
is_asset_account: &F1,
is_income_account: &F2,
is_expense_account: &F3,
main_commodity: &str,
main_commodity_decimal_points: u32,
prices: &Prices,
) -> Result<(), PricesError>
where
F1: Fn(&str) -> bool,
F2: Fn(&str) -> bool,
F3: Fn(&str) -> bool,
{
for transaction in &mut ledger.transactions {
handle_foreign_asset_income(
transaction,
is_income_account,
main_commodity,
main_commodity_decimal_points,
prices,
)?;
handle_asset_exchange(transaction, is_asset_account);
handle_foreign_asset_expenses(
transaction,
is_expense_account,
main_commodity,
main_commodity_decimal_points,
prices,
)?;
}
Ok(())
}
fn handle_foreign_asset_income<F>(
transaction: &mut Transaction,
is_income_account: &F,
main_commodity: &str,
main_commodity_decimal_points: u32,
prices: &Prices,
) -> Result<(), PricesError>
where
F: Fn(&str) -> bool,
{
let mut new_postings = Vec::new();
for posting in transaction.postings.iter_mut() {
if is_income_account(&posting.account) && posting.amount.commodity.name != main_commodity {
let foreign_amount = posting.amount.clone();
let mut amount_main_commodity = prices.convert(
posting.amount.quantity,
&posting.amount.commodity.name,
main_commodity,
transaction.date,
)?;
amount_main_commodity = amount_main_commodity.round_dp_with_strategy(
main_commodity_decimal_points,
RoundingStrategy::MidpointAwayFromZero,
);
let mut main_currency_amount = Amount {
quantity: amount_main_commodity,
commodity: Commodity {
name: main_commodity.to_string(),
position: CommodityPosition::Right,
},
};
posting.amount = main_currency_amount.clone();
main_currency_amount.quantity = -main_currency_amount.quantity;
new_postings.push(Posting {
date: posting.date,
effective_date: posting.effective_date,
comment: Some("Auto-generated".to_string()),
account: "Trading:Exchange".to_string(),
reality: Reality::Real,
status: None,
amount: main_currency_amount,
tags: vec![],
});
new_postings.push(Posting {
date: posting.date,
effective_date: posting.effective_date,
comment: Some("Auto-generated".to_string()),
account: "Trading:Exchange".to_string(),
reality: Reality::Real,
status: None,
amount: foreign_amount,
tags: vec![],
});
}
}
transaction.postings.append(&mut new_postings);
Ok(())
}
fn handle_asset_exchange<F>(transaction: &mut Transaction, is_asset_account: &F)
where
F: Fn(&str) -> bool,
{
if transaction.postings.len() != 2 {
return;
}
let posting1 = &transaction.postings[0];
let posting2 = &transaction.postings[1];
if !is_asset_account(&posting1.account) || !is_asset_account(&posting2.account) {
return;
}
let commodity1_name = &posting1.amount.commodity.name;
let commodity2_name = &posting2.amount.commodity.name;
if commodity1_name == commodity2_name {
return;
}
let mut amount1 = posting1.amount.clone();
let mut amount2 = posting2.amount.clone();
amount1.quantity = -amount1.quantity;
amount2.quantity = -amount2.quantity;
let new_posting1 = Posting {
date: posting1.date,
effective_date: posting1.effective_date,
comment: Some("Auto-generated".to_string()),
account: "Trading:Exchange".to_string(),
reality: Reality::Real,
status: posting1.status,
amount: amount1,
tags: posting1.tags.clone(),
};
let new_posting2 = Posting {
date: posting2.date,
effective_date: posting2.effective_date,
comment: Some("Auto-generated".to_string()),
account: "Trading:Exchange".to_string(),
reality: Reality::Real,
status: posting2.status,
amount: amount2,
tags: posting2.tags.clone(),
};
transaction.postings.push(new_posting1);
transaction.postings.push(new_posting2);
}
fn handle_foreign_asset_expenses<F>(
transaction: &mut Transaction,
is_expense_account: &F,
main_commodity: &str,
main_commodity_decimal_points: u32,
prices: &Prices,
) -> Result<(), PricesError>
where
F: Fn(&str) -> bool,
{
let mut new_postings = Vec::new();
for posting in transaction.postings.iter_mut() {
if is_expense_account(&posting.account) && posting.amount.commodity.name != main_commodity {
let foreign_amount = posting.amount.clone();
let mut amount_main_commodity = prices.convert(
posting.amount.quantity,
&posting.amount.commodity.name,
main_commodity,
transaction.date,
)?;
amount_main_commodity = amount_main_commodity.round_dp_with_strategy(
main_commodity_decimal_points,
RoundingStrategy::MidpointAwayFromZero,
);
let mut main_currency_amount = Amount {
quantity: amount_main_commodity,
commodity: Commodity {
name: main_commodity.to_string(),
position: CommodityPosition::Right,
},
};
posting.amount = main_currency_amount.clone();
main_currency_amount.quantity = -main_currency_amount.quantity;
new_postings.push(Posting {
date: posting.date,
effective_date: posting.effective_date,
comment: Some("Auto-generated".to_string()),
account: "Trading:Exchange".to_string(),
reality: Reality::Real,
status: posting.status,
amount: main_currency_amount,
tags: posting.tags.clone(),
});
new_postings.push(Posting {
date: posting.date,
effective_date: posting.effective_date,
comment: Some("Auto-generated".to_string()),
account: "Trading:Exchange".to_string(),
reality: Reality::Real,
status: posting.status,
amount: foreign_amount,
tags: posting.tags.clone(),
});
}
}
transaction.postings.append(&mut new_postings);
Ok(())
}