Merge branch 'product_creater' into 'main'
Add pass flushing for move money See merge request vato007/coster-rs!2
This commit is contained in:
10
src/lib.rs
10
src/lib.rs
@@ -14,10 +14,14 @@ pub use self::overhead_allocation::*;
|
|||||||
mod create_products;
|
mod create_products;
|
||||||
pub use self::create_products::*;
|
pub use self::create_products::*;
|
||||||
|
|
||||||
|
mod shared_models;
|
||||||
|
pub use self::shared_models::*;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn move_money_from_text(
|
pub extern "C" fn move_money_from_text(
|
||||||
rules: *const c_char,
|
rules: *const c_char,
|
||||||
lines: *const c_char,
|
lines: *const c_char,
|
||||||
|
accounts: *const c_char,
|
||||||
use_numeric_accounts: bool,
|
use_numeric_accounts: bool,
|
||||||
) -> *mut c_char {
|
) -> *mut c_char {
|
||||||
let mut output_writer = csv::Writer::from_writer(vec![]);
|
let mut output_writer = csv::Writer::from_writer(vec![]);
|
||||||
@@ -29,11 +33,17 @@ pub extern "C" fn move_money_from_text(
|
|||||||
assert!(!lines.is_null());
|
assert!(!lines.is_null());
|
||||||
CStr::from_ptr(lines)
|
CStr::from_ptr(lines)
|
||||||
};
|
};
|
||||||
|
let safe_accounts = unsafe {
|
||||||
|
assert!(!lines.is_null());
|
||||||
|
CStr::from_ptr(accounts)
|
||||||
|
};
|
||||||
move_money(
|
move_money(
|
||||||
csv::Reader::from_reader(safe_rules.to_bytes()),
|
csv::Reader::from_reader(safe_rules.to_bytes()),
|
||||||
csv::Reader::from_reader(safe_lines.to_bytes()),
|
csv::Reader::from_reader(safe_lines.to_bytes()),
|
||||||
|
csv::Reader::from_reader(safe_accounts.to_bytes()),
|
||||||
&mut output_writer,
|
&mut output_writer,
|
||||||
use_numeric_accounts,
|
use_numeric_accounts,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
// TODO: Replace all these unwraps with something more elegant
|
// TODO: Replace all these unwraps with something more elegant
|
||||||
let inner = output_writer.into_inner().unwrap();
|
let inner = output_writer.into_inner().unwrap();
|
||||||
|
|||||||
21
src/main.rs
21
src/main.rs
@@ -24,11 +24,17 @@ enum Commands {
|
|||||||
#[arg(short = 'l', long, value_name = "FILE")]
|
#[arg(short = 'l', long, value_name = "FILE")]
|
||||||
lines: PathBuf,
|
lines: PathBuf,
|
||||||
|
|
||||||
|
#[arg(short = 'a', long, value_name = "FILE")]
|
||||||
|
accounts: PathBuf,
|
||||||
|
|
||||||
#[arg(short, long, value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
use_numeric_accounts: bool,
|
use_numeric_accounts: bool,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
flush_pass: bool,
|
||||||
},
|
},
|
||||||
/// Combines rules to the minimum set required
|
/// Combines rules to the minimum set required
|
||||||
smush_rules {
|
smush_rules {
|
||||||
@@ -73,9 +79,18 @@ fn main() -> anyhow::Result<()> {
|
|||||||
Commands::move_money {
|
Commands::move_money {
|
||||||
rules,
|
rules,
|
||||||
lines,
|
lines,
|
||||||
|
accounts,
|
||||||
output,
|
output,
|
||||||
use_numeric_accounts,
|
use_numeric_accounts,
|
||||||
} => move_money(rules, lines, output, use_numeric_accounts),
|
flush_pass,
|
||||||
|
} => move_money(
|
||||||
|
rules,
|
||||||
|
lines,
|
||||||
|
accounts,
|
||||||
|
output,
|
||||||
|
use_numeric_accounts,
|
||||||
|
flush_pass,
|
||||||
|
),
|
||||||
Commands::smush_rules { rules, output } => smush_rules(rules, output),
|
Commands::smush_rules { rules, output } => smush_rules(rules, output),
|
||||||
Commands::allocate_overheads {
|
Commands::allocate_overheads {
|
||||||
lines,
|
lines,
|
||||||
@@ -102,14 +117,18 @@ fn main() -> anyhow::Result<()> {
|
|||||||
fn move_money(
|
fn move_money(
|
||||||
rules: PathBuf,
|
rules: PathBuf,
|
||||||
lines: PathBuf,
|
lines: PathBuf,
|
||||||
|
accounts: PathBuf,
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
use_numeric_accounts: bool,
|
use_numeric_accounts: bool,
|
||||||
|
flush_pass: bool,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
coster_rs::move_money(
|
coster_rs::move_money(
|
||||||
csv::Reader::from_path(rules)?,
|
csv::Reader::from_path(rules)?,
|
||||||
csv::Reader::from_path(lines)?,
|
csv::Reader::from_path(lines)?,
|
||||||
|
csv::Reader::from_path(accounts)?,
|
||||||
&mut csv::Writer::from_path(output.unwrap_or(PathBuf::from("output.csv")))?,
|
&mut csv::Writer::from_path(output.unwrap_or(PathBuf::from("output.csv")))?,
|
||||||
use_numeric_accounts,
|
use_numeric_accounts,
|
||||||
|
flush_pass,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, thread::current};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
|
|
||||||
|
use crate::CsvAccount;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct CsvMovementRule {
|
struct CsvMovementRule {
|
||||||
#[serde(rename = "CostCentreSourceFrom", default)]
|
#[serde(rename = "CostCentreSourceFrom", default)]
|
||||||
@@ -36,6 +38,8 @@ struct CsvMovementRule {
|
|||||||
is_percent: String,
|
is_percent: String,
|
||||||
#[serde(rename = "Apply", default)]
|
#[serde(rename = "Apply", default)]
|
||||||
apply: String,
|
apply: String,
|
||||||
|
#[serde(rename = "CostOutput")]
|
||||||
|
cost_output: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -88,6 +92,7 @@ pub struct CsvCost {
|
|||||||
pub department: String,
|
pub department: String,
|
||||||
#[serde(serialize_with = "round_serialize")]
|
#[serde(serialize_with = "round_serialize")]
|
||||||
pub value: f64,
|
pub value: f64,
|
||||||
|
pub pass: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn round_serialize<S>(x: &f64, s: S) -> Result<S::Ok, S::Error>
|
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.)
|
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>,
|
rules_reader: csv::Reader<R>,
|
||||||
lines_reader: csv::Reader<L>,
|
lines_reader: csv::Reader<L>,
|
||||||
|
accounts_reader: csv::Reader<A>,
|
||||||
output: &mut csv::Writer<O>,
|
output: &mut csv::Writer<O>,
|
||||||
use_numeric_accounts: bool,
|
use_numeric_accounts: bool,
|
||||||
|
flush_pass: bool,
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
R: std::io::Read,
|
R: std::io::Read,
|
||||||
L: std::io::Read,
|
L: std::io::Read,
|
||||||
|
A: std::io::Read,
|
||||||
O: std::io::Write,
|
O: std::io::Write,
|
||||||
{
|
{
|
||||||
let mut lines_reader = lines_reader;
|
let mut lines_reader = lines_reader;
|
||||||
@@ -146,6 +154,14 @@ where
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.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 {
|
let all_accounts_sorted = if use_numeric_accounts {
|
||||||
lines
|
lines
|
||||||
@@ -172,6 +188,7 @@ where
|
|||||||
let mut rules_reader = rules_reader;
|
let mut rules_reader = rules_reader;
|
||||||
let mut rules: Vec<MovementRule> = vec![];
|
let mut rules: Vec<MovementRule> = vec![];
|
||||||
for movement_rule in rules_reader.deserialize() {
|
for movement_rule in rules_reader.deserialize() {
|
||||||
|
// TODO: Consider reclass rule group, how does that even work?
|
||||||
let movement_rule: CsvMovementRule = movement_rule?;
|
let movement_rule: CsvMovementRule = movement_rule?;
|
||||||
let from_accounts = extract_range(
|
let from_accounts = extract_range(
|
||||||
movement_rule.source_from_account,
|
movement_rule.source_from_account,
|
||||||
@@ -179,12 +196,24 @@ where
|
|||||||
false,
|
false,
|
||||||
&all_accounts_sorted,
|
&all_accounts_sorted,
|
||||||
);
|
);
|
||||||
let to_accounts = extract_range(
|
let to_accounts = if movement_rule.cost_output.is_some() {
|
||||||
movement_rule.dest_from_account,
|
account_mappings
|
||||||
movement_rule.dest_to_account,
|
.iter()
|
||||||
false,
|
.filter(|(_, account)| {
|
||||||
&all_accounts_sorted,
|
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(
|
let from_departments = extract_range(
|
||||||
movement_rule.source_from_department,
|
movement_rule.source_from_department,
|
||||||
movement_rule.source_to_department,
|
movement_rule.source_to_department,
|
||||||
@@ -218,15 +247,18 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then run move_money
|
// 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
|
// Ouput the list moved moneys
|
||||||
for money in moved {
|
for money in moved {
|
||||||
output.serialize(CsvCost {
|
for (unit, value) in money.totals {
|
||||||
account: money.0.account,
|
output.serialize(CsvCost {
|
||||||
department: money.0.department,
|
account: unit.account,
|
||||||
value: money.1,
|
department: unit.department,
|
||||||
})?;
|
value,
|
||||||
|
pass: if flush_pass { Some(money.pass) } else { None },
|
||||||
|
})?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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.
|
// Advantage of this approach is it can be easily extended to run on the gpu.
|
||||||
pub fn move_money_1() {}
|
pub fn move_money_1() {}
|
||||||
|
|
||||||
|
pub struct MoveMoneyResult {
|
||||||
|
pass: i32,
|
||||||
|
totals: HashMap<Unit, f64>,
|
||||||
|
}
|
||||||
|
|
||||||
// Approach 2:
|
// Approach 2:
|
||||||
// Traditinoal/naive, total for each department is stored in an initial map (department -> total amount).
|
// 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
|
// 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(
|
pub fn move_money_2(
|
||||||
initial_totals: HashMap<Unit, f64>,
|
initial_totals: HashMap<Unit, f64>,
|
||||||
rules: &Vec<MovementRule>,
|
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),
|
// 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
|
// 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 running_total = HashMap::from(initial_totals);
|
||||||
let mut temp_total = running_total.clone();
|
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 {
|
for rule in rules {
|
||||||
if rule.is_separator {
|
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();
|
running_total = temp_total.clone();
|
||||||
} else {
|
} else {
|
||||||
let mut sum_from = 0.;
|
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)]
|
#[cfg(test)]
|
||||||
@@ -333,7 +396,9 @@ mod tests {
|
|||||||
super::move_money(
|
super::move_money(
|
||||||
csv::Reader::from_path("reclassrule.csv").unwrap(),
|
csv::Reader::from_path("reclassrule.csv").unwrap(),
|
||||||
csv::Reader::from_path("line.csv").unwrap(),
|
csv::Reader::from_path("line.csv").unwrap(),
|
||||||
|
csv::Reader::from_path("account.csv").unwrap(),
|
||||||
&mut csv::Writer::from_path("output.csv").unwrap(),
|
&mut csv::Writer::from_path("output.csv").unwrap(),
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use itertools::Itertools;
|
|||||||
use nalgebra::{DMatrix, Dynamic, LU};
|
use nalgebra::{DMatrix, Dynamic, LU};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::CsvCost;
|
use crate::{CsvAccount, CsvCost};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum DepartmentType {
|
pub enum DepartmentType {
|
||||||
@@ -39,20 +39,6 @@ pub struct AllocationStatisticAccountRange {
|
|||||||
end: usize,
|
end: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct CsvAccount {
|
|
||||||
#[serde(rename = "Code")]
|
|
||||||
code: String,
|
|
||||||
#[serde(rename = "Description")]
|
|
||||||
description: Option<String>,
|
|
||||||
#[serde(rename = "Type")]
|
|
||||||
account_type: String,
|
|
||||||
#[serde(rename = "CostOutput")]
|
|
||||||
cost_output: Option<String>,
|
|
||||||
#[serde(rename = "PercentFixed")]
|
|
||||||
percent_fixed: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
type CsvCostCentre = HashMap<String, String>;
|
type CsvCostCentre = HashMap<String, String>;
|
||||||
|
|
||||||
type CsvArea = HashMap<String, String>;
|
type CsvArea = HashMap<String, String>;
|
||||||
@@ -393,6 +379,7 @@ where
|
|||||||
account: cost.account.clone(),
|
account: cost.account.clone(),
|
||||||
department: department.department,
|
department: department.department,
|
||||||
value: department.value,
|
value: department.value,
|
||||||
|
pass: None,
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/shared_models.rs
Normal file
15
src/shared_models.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CsvAccount {
|
||||||
|
#[serde(rename = "Code")]
|
||||||
|
pub code: String,
|
||||||
|
#[serde(rename = "Description")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[serde(rename = "Type")]
|
||||||
|
pub account_type: String,
|
||||||
|
#[serde(rename = "CostOutput")]
|
||||||
|
pub cost_output: Option<String>,
|
||||||
|
#[serde(rename = "PercentFixed")]
|
||||||
|
pub percent_fixed: f64,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user