Add movements between accounts and departments

This commit is contained in:
Piv
2023-01-28 11:07:13 +10:30
parent 37a7b333ac
commit 10f24f65ac

View File

@@ -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<String>,
pub to_units: Vec<String>,
pub from_departments: Vec<String>,
pub to_departments: Vec<String>,
pub all_from_departments: bool,
pub all_to_departments: bool,
pub from_accounts: Vec<String>,
pub to_accounts: Vec<String>,
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<MovementRule>) -> Vec<MovementRule> {
// 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<String, f64>,
rules: Vec<MovementRule>,
) -> HashMap<String, f64> {
initial_totals: HashMap<Unit, f64>,
rules: &Vec<MovementRule>,
) -> HashMap<Unit, f64> {
// 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<String, f64> = 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",
);
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
previous_temp * rule.amount
} else {
rule.amount
};
sum_from += added_amount;
*temp_total.get_mut(&department).unwrap() -= 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;
}
}
}