From 10f24f65ac7d19bf82ee2966fa8bad2be68eb359 Mon Sep 17 00:00:00 2001 From: Piv <18462828+Piv200@users.noreply.github.com> Date: Sat, 28 Jan 2023 11:07:13 +1030 Subject: [PATCH] Add movements between accounts and departments --- src/lib.rs | 76 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 58174bd..beb69cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,23 @@ use itertools::Itertools; use na::{DMatrix, Dynamic, LU}; use std::{collections::HashMap, error::Error, ops::Mul}; -// TODO: Look into serde for serialisation, can also use it to serialise/deserialise -// records from a csv file using the csv crate +#[derive(Hash, Clone, Default, PartialEq, Eq)] +pub struct Unit { + pub department: String, + pub account: String, +} + #[derive(Default)] pub struct MovementRule { // If the vectors are empty, then it means 'all' - pub from_units: Vec, - pub to_units: Vec, + pub from_departments: Vec, + pub to_departments: Vec, + pub all_from_departments: bool, + pub all_to_departments: bool, + pub from_accounts: Vec, + pub to_accounts: Vec, + pub all_from_accounts: bool, + pub all_to_accounts: bool, pub amount: f64, pub is_percent: bool, pub is_separator: bool, @@ -19,16 +29,13 @@ pub struct MovementRule { impl MovementRule { pub fn pass_break() -> MovementRule { MovementRule { - from_units: vec![], - to_units: vec![], - amount: 0.0, - is_percent: false, is_separator: true, + ..MovementRule::default() } } pub fn validate(&self) -> bool { - if self.from_units.is_empty() && self.to_units.is_empty() { + if self.from_departments.is_empty() && self.to_departments.is_empty() { // Would be nice to have a decent message/error here as well return false; } @@ -51,7 +58,7 @@ pub fn smush_rules(rules: Vec) -> Vec { // TODO: We could make this more advanced by only smushing per divider, so that only the departments // needed between each pass is actually required for rule in rules { - for department in rule.from_units { + for department in rule.from_departments { // ruleMapping.entry(department).or_insert(ruleMapping.len()); } } @@ -77,40 +84,45 @@ pub fn move_money_1() {} // Note that the movement happens on a line-by-line level. So we can stream the data from disk, and potentially apply this // to every. It's also much more memory efficient than approach 1. // TODO: Time both approaches to seee which is faster depending on the size of the input data/number of rules -// TODO: Right now this only supports movements between departments, we also need to support movements between accounts. -// This would require an expansion so that we also have from/to accounts, and the hashmap will use some struct -// that combines an account/department, which is also how the totals will be loaded. (so when loading from disk, -// we load the whole GL into memory sum the account/department totals, and move these into a map line by line) pub fn move_money_2( - initial_totals: HashMap, - rules: Vec, -) -> HashMap { + initial_totals: HashMap, + rules: &Vec, +) -> HashMap { // TODO: Should probably validate that all the rules have departments that actually exist in initial_totals. // Note: It's potentially a bit more intensive to use cloned totals, but it's much simpler code and, and since we're only working line-by-line // it isn't really that much memory. in practice let mut running_total = HashMap::from(initial_totals); - let mut temp_total: HashMap = running_total.clone(); + let mut temp_total = running_total.clone(); for rule in rules { if rule.is_separator { running_total = temp_total.clone(); } else { let mut sum_from = 0.; - for department in rule.from_units { - let previous_temp = running_total.get(&department).expect( - "Failed to find department in temp totals, this should not be possible", - ); - let added_amount = if rule.is_percent { - *previous_temp * rule.amount - } else { - rule.amount - }; - sum_from += added_amount; - *temp_total.get_mut(&department).unwrap() -= added_amount; + for unit in &running_total { + if (rule.all_from_departments || rule.from_departments.contains(&unit.0.department)) + && (rule.all_from_accounts || rule.from_accounts.contains(&unit.0.account)) + { + let previous_temp = unit.1; + let added_amount = if rule.is_percent { + previous_temp * rule.amount + } else { + rule.amount + }; + sum_from += added_amount; + *temp_total.get_mut(&unit.0).unwrap() -= added_amount; + } } - let value_per_unit = sum_from / rule.to_units.len() as f64; - for department in rule.to_units { - *temp_total.get_mut(&department).unwrap() += value_per_unit; + let num_to_units = running_total + .keys() + .filter(|key| { + (rule.all_to_accounts || rule.to_departments.contains(&key.department)) + && (rule.all_to_accounts || rule.to_accounts.contains(&key.account)) + }) + .count(); + let value_per_unit = sum_from / num_to_units as f64; + for unit in running_total.keys() { + *temp_total.get_mut(&unit).unwrap() += value_per_unit; } } }