Add movements between accounts and departments
This commit is contained in:
66
src/lib.rs
66
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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user