Lines
100 %
Functions
33.33 %
Branches
use finance::error::ReportError;
use num_rational::Rational64;
use sqlx::types::Uuid;
use std::collections::HashMap;
use super::super::{CommodityAmount, ReportNode};
pub(super) struct AccountRow {
pub account_id: Uuid,
pub parent_id: Option<Uuid>,
pub account_name: String,
}
pub(super) type AmountMap = HashMap<Uuid, (Rational64, String)>;
pub(super) type AccountAmounts = HashMap<Uuid, AmountMap>;
pub(super) fn accumulate_split(
amounts: &mut AccountAmounts,
account_id: Uuid,
commodity_id: Uuid,
value: Rational64,
symbol: &str,
) {
amounts
.entry(account_id)
.or_default()
.entry(commodity_id)
.and_modify(|(sum, _)| *sum += value)
.or_insert_with(|| (value, symbol.to_owned()));
pub(super) struct ConversionTarget<'a> {
pub commodity_id: Uuid,
pub symbol: &'a str,
pub(super) fn accumulate_split_converted(
target: &ConversionTarget<'_>,
price: (Option<i64>, Option<i64>),
) -> Result<(), ReportError> {
if commodity_id == target.commodity_id {
accumulate_split(
amounts,
account_id,
target.commodity_id,
value,
target.symbol,
);
Ok(())
} else if let (Some(pn), Some(pd)) = price {
let converted = value * Rational64::new(pn, pd);
converted,
} else {
Err(ReportError::MissingConversion {
from_commodity: symbol.to_owned(),
to_commodity: target.symbol.to_owned(),
})
pub(super) fn build_tree(accounts: &[AccountRow], amounts: &AccountAmounts) -> Vec<ReportNode> {
let children_map: HashMap<Option<Uuid>, Vec<&AccountRow>> = {
let mut m: HashMap<Option<Uuid>, Vec<&AccountRow>> = HashMap::new();
for a in accounts {
m.entry(a.parent_id).or_default().push(a);
m
};
let roots = children_map.get(&None).cloned().unwrap_or_default();
roots
.iter()
.map(|a| build_node(a, &children_map, amounts, "", 0))
.filter(|n| !n.amounts.is_empty())
.collect()
fn build_node(
account: &AccountRow,
children_map: &HashMap<Option<Uuid>, Vec<&AccountRow>>,
amounts: &AccountAmounts,
parent_path: &str,
depth: usize,
) -> ReportNode {
let path = if parent_path.is_empty() {
account.account_name.clone()
format!("{parent_path}:{}", account.account_name)
let children: Vec<ReportNode> = children_map
.get(&Some(account.account_id))
.cloned()
.unwrap_or_default()
.map(|child| build_node(child, children_map, amounts, &path, depth + 1))
.collect();
let mut own_amounts: AmountMap = amounts
.get(&account.account_id)
.unwrap_or_default();
for child in &children {
for ca in &child.amounts {
own_amounts
.entry(ca.commodity_id)
.and_modify(|(sum, _)| *sum += ca.amount)
.or_insert_with(|| (ca.amount, ca.commodity_symbol.clone()));
let children: Vec<ReportNode> = children
.into_iter()
let mut sorted_amounts: Vec<CommodityAmount> = own_amounts
.map(|(cid, (amount, symbol))| CommodityAmount {
commodity_id: cid,
commodity_symbol: symbol,
amount,
sorted_amounts.sort_by_key(|ca| ca.commodity_id);
ReportNode {
account_id: account.account_id,
account_name: account.account_name.clone(),
account_path: path,
depth,
amounts: sorted_amounts,
children,