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 na::{DMatrix, Dynamic, LU};
|
||||||
use std::{collections::HashMap, error::Error, ops::Mul};
|
use std::{collections::HashMap, error::Error, ops::Mul};
|
||||||
|
|
||||||
// TODO: Look into serde for serialisation, can also use it to serialise/deserialise
|
#[derive(Hash, Clone, Default, PartialEq, Eq)]
|
||||||
// records from a csv file using the csv crate
|
pub struct Unit {
|
||||||
|
pub department: String,
|
||||||
|
pub account: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MovementRule {
|
pub struct MovementRule {
|
||||||
// If the vectors are empty, then it means 'all'
|
// If the vectors are empty, then it means 'all'
|
||||||
pub from_units: Vec<String>,
|
pub from_departments: Vec<String>,
|
||||||
pub to_units: 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 amount: f64,
|
||||||
pub is_percent: bool,
|
pub is_percent: bool,
|
||||||
pub is_separator: bool,
|
pub is_separator: bool,
|
||||||
@@ -19,16 +29,13 @@ pub struct MovementRule {
|
|||||||
impl MovementRule {
|
impl MovementRule {
|
||||||
pub fn pass_break() -> MovementRule {
|
pub fn pass_break() -> MovementRule {
|
||||||
MovementRule {
|
MovementRule {
|
||||||
from_units: vec![],
|
|
||||||
to_units: vec![],
|
|
||||||
amount: 0.0,
|
|
||||||
is_percent: false,
|
|
||||||
is_separator: true,
|
is_separator: true,
|
||||||
|
..MovementRule::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self) -> bool {
|
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
|
// Would be nice to have a decent message/error here as well
|
||||||
return false;
|
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
|
// TODO: We could make this more advanced by only smushing per divider, so that only the departments
|
||||||
// needed between each pass is actually required
|
// needed between each pass is actually required
|
||||||
for rule in rules {
|
for rule in rules {
|
||||||
for department in rule.from_units {
|
for department in rule.from_departments {
|
||||||
// ruleMapping.entry(department).or_insert(ruleMapping.len());
|
// 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
|
// 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.
|
// 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: 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(
|
pub fn move_money_2(
|
||||||
initial_totals: HashMap<String, f64>,
|
initial_totals: HashMap<Unit, f64>,
|
||||||
rules: Vec<MovementRule>,
|
rules: &Vec<MovementRule>,
|
||||||
) -> HashMap<String, f64> {
|
) -> HashMap<Unit, f64> {
|
||||||
// TODO: Should probably validate that all the rules have departments that actually exist in initial_totals.
|
// 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
|
// 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
|
// it isn't really that much memory. in practice
|
||||||
let mut running_total = HashMap::from(initial_totals);
|
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 {
|
for rule in rules {
|
||||||
if rule.is_separator {
|
if rule.is_separator {
|
||||||
running_total = temp_total.clone();
|
running_total = temp_total.clone();
|
||||||
} else {
|
} else {
|
||||||
let mut sum_from = 0.;
|
let mut sum_from = 0.;
|
||||||
for department in rule.from_units {
|
for unit in &running_total {
|
||||||
let previous_temp = running_total.get(&department).expect(
|
if (rule.all_from_departments || rule.from_departments.contains(&unit.0.department))
|
||||||
"Failed to find department in temp totals, this should not be possible",
|
&& (rule.all_from_accounts || rule.from_accounts.contains(&unit.0.account))
|
||||||
);
|
{
|
||||||
|
let previous_temp = unit.1;
|
||||||
let added_amount = if rule.is_percent {
|
let added_amount = if rule.is_percent {
|
||||||
*previous_temp * rule.amount
|
previous_temp * rule.amount
|
||||||
} else {
|
} else {
|
||||||
rule.amount
|
rule.amount
|
||||||
};
|
};
|
||||||
sum_from += added_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;
|
let num_to_units = running_total
|
||||||
for department in rule.to_units {
|
.keys()
|
||||||
*temp_total.get_mut(&department).unwrap() += value_per_unit;
|
.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