Add pass flushing for move money
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, thread::current};
|
||||
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::CsvAccount;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CsvMovementRule {
|
||||
#[serde(rename = "CostCentreSourceFrom", default)]
|
||||
@@ -36,6 +38,8 @@ struct CsvMovementRule {
|
||||
is_percent: String,
|
||||
#[serde(rename = "Apply", default)]
|
||||
apply: String,
|
||||
#[serde(rename = "CostOutput")]
|
||||
cost_output: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -88,6 +92,7 @@ pub struct CsvCost {
|
||||
pub department: String,
|
||||
#[serde(serialize_with = "round_serialize")]
|
||||
pub value: f64,
|
||||
pub pass: Option<i32>,
|
||||
}
|
||||
|
||||
fn round_serialize<S>(x: &f64, s: S) -> Result<S::Ok, S::Error>
|
||||
@@ -97,15 +102,18 @@ where
|
||||
s.serialize_f64((x * 100000.).round() / 100000.)
|
||||
}
|
||||
|
||||
pub fn move_money<R, L, O>(
|
||||
pub fn move_money<R, L, A, O>(
|
||||
rules_reader: csv::Reader<R>,
|
||||
lines_reader: csv::Reader<L>,
|
||||
accounts_reader: csv::Reader<A>,
|
||||
output: &mut csv::Writer<O>,
|
||||
use_numeric_accounts: bool,
|
||||
flush_pass: bool,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
R: std::io::Read,
|
||||
L: std::io::Read,
|
||||
A: std::io::Read,
|
||||
O: std::io::Write,
|
||||
{
|
||||
let mut lines_reader = lines_reader;
|
||||
@@ -146,6 +154,14 @@ where
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let mut accounts_reader = accounts_reader;
|
||||
let all_accounts = accounts_reader
|
||||
.deserialize()
|
||||
.collect::<Result<Vec<CsvAccount>, csv::Error>>()?;
|
||||
let account_mappings: HashMap<String, CsvAccount> = all_accounts
|
||||
.into_iter()
|
||||
.map(|account| (account.code.clone(), account))
|
||||
.collect();
|
||||
|
||||
let all_accounts_sorted = if use_numeric_accounts {
|
||||
lines
|
||||
@@ -172,6 +188,7 @@ where
|
||||
let mut rules_reader = rules_reader;
|
||||
let mut rules: Vec<MovementRule> = vec![];
|
||||
for movement_rule in rules_reader.deserialize() {
|
||||
// TODO: Consider reclass rule group, how does that even work?
|
||||
let movement_rule: CsvMovementRule = movement_rule?;
|
||||
let from_accounts = extract_range(
|
||||
movement_rule.source_from_account,
|
||||
@@ -179,12 +196,24 @@ where
|
||||
false,
|
||||
&all_accounts_sorted,
|
||||
);
|
||||
let to_accounts = extract_range(
|
||||
movement_rule.dest_from_account,
|
||||
movement_rule.dest_to_account,
|
||||
false,
|
||||
&all_accounts_sorted,
|
||||
);
|
||||
let to_accounts = if movement_rule.cost_output.is_some() {
|
||||
account_mappings
|
||||
.iter()
|
||||
.filter(|(_, account)| {
|
||||
account.cost_output.is_some()
|
||||
&& account.cost_output.clone().unwrap()
|
||||
== movement_rule.cost_output.clone().unwrap()
|
||||
})
|
||||
.map(|(code, _)| code.clone())
|
||||
.collect()
|
||||
} else {
|
||||
extract_range(
|
||||
movement_rule.dest_from_account,
|
||||
movement_rule.dest_to_account,
|
||||
false,
|
||||
&all_accounts_sorted,
|
||||
)
|
||||
};
|
||||
let from_departments = extract_range(
|
||||
movement_rule.source_from_department,
|
||||
movement_rule.source_to_department,
|
||||
@@ -218,15 +247,18 @@ where
|
||||
}
|
||||
|
||||
// Then run move_money
|
||||
let moved = move_money_2(lines, &rules);
|
||||
let moved = move_money_2(lines, &rules, flush_pass);
|
||||
|
||||
// Ouput the list moved moneys
|
||||
for money in moved {
|
||||
output.serialize(CsvCost {
|
||||
account: money.0.account,
|
||||
department: money.0.department,
|
||||
value: money.1,
|
||||
})?;
|
||||
for (unit, value) in money.totals {
|
||||
output.serialize(CsvCost {
|
||||
account: unit.account,
|
||||
department: unit.department,
|
||||
value,
|
||||
pass: if flush_pass { Some(money.pass) } else { None },
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -267,6 +299,11 @@ fn extract_range(from: String, to: String, all: bool, options: &Vec<String>) ->
|
||||
// Advantage of this approach is it can be easily extended to run on the gpu.
|
||||
pub fn move_money_1() {}
|
||||
|
||||
pub struct MoveMoneyResult {
|
||||
pass: i32,
|
||||
totals: HashMap<Unit, f64>,
|
||||
}
|
||||
|
||||
// Approach 2:
|
||||
// Traditinoal/naive, total for each department is stored in an initial map (department -> total amount).
|
||||
// Another map is built up for each rule, and each rule is processed based on the amount in the current total
|
||||
@@ -281,13 +318,34 @@ pub fn move_money_1() {}
|
||||
pub fn move_money_2(
|
||||
initial_totals: HashMap<Unit, f64>,
|
||||
rules: &Vec<MovementRule>,
|
||||
) -> HashMap<Unit, f64> {
|
||||
flush_pass: bool,
|
||||
) -> Vec<MoveMoneyResult> {
|
||||
// Note: It's potentially a bit more intensive to use cloned totals (rather than just update temp_total per rule),
|
||||
// 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 = running_total.clone();
|
||||
let mut moveMoneyResult: Vec<MoveMoneyResult> = vec![];
|
||||
let mut current_pass = 0;
|
||||
if flush_pass {
|
||||
moveMoneyResult.push(MoveMoneyResult {
|
||||
pass: current_pass,
|
||||
totals: running_total.clone(),
|
||||
})
|
||||
}
|
||||
for rule in rules {
|
||||
if rule.is_separator {
|
||||
if flush_pass {
|
||||
// Flush the totals at the end of this pass (more specifically the change)
|
||||
moveMoneyResult.push(MoveMoneyResult {
|
||||
pass: current_pass,
|
||||
totals: running_total
|
||||
.iter()
|
||||
.map(|(unit, value)| (unit.clone(), temp_total.get(unit).unwrap() - value))
|
||||
.filter(|(_, value)| *value != 0.)
|
||||
.collect(),
|
||||
});
|
||||
current_pass += 1;
|
||||
}
|
||||
running_total = temp_total.clone();
|
||||
} else {
|
||||
let mut sum_from = 0.;
|
||||
@@ -323,7 +381,12 @@ pub fn move_money_2(
|
||||
}
|
||||
}
|
||||
}
|
||||
temp_total
|
||||
moveMoneyResult.push(MoveMoneyResult {
|
||||
pass: current_pass,
|
||||
totals: temp_total,
|
||||
});
|
||||
|
||||
moveMoneyResult
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -333,7 +396,9 @@ mod tests {
|
||||
super::move_money(
|
||||
csv::Reader::from_path("reclassrule.csv").unwrap(),
|
||||
csv::Reader::from_path("line.csv").unwrap(),
|
||||
csv::Reader::from_path("account.csv").unwrap(),
|
||||
&mut csv::Writer::from_path("output.csv").unwrap(),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user