Start adding overhead allocation load from file
This commit is contained in:
@@ -83,10 +83,10 @@ pub struct Unit {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct CsvCost {
|
pub struct CsvCost {
|
||||||
#[serde(rename = "ACCOUNT")]
|
#[serde(rename = "ACCOUNT")]
|
||||||
account: String,
|
pub account: String,
|
||||||
#[serde(rename = "COSTCENTRE")]
|
#[serde(rename = "COSTCENTRE")]
|
||||||
department: String,
|
pub department: String,
|
||||||
value: f64,
|
pub value: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_money<R, L, O>(
|
pub fn move_money<R, L, O>(
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ use itertools::Itertools;
|
|||||||
use nalgebra::{DMatrix, Dynamic, LU};
|
use nalgebra::{DMatrix, Dynamic, LU};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::CsvCost;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum DepartmentType {
|
pub enum DepartmentType {
|
||||||
Operating,
|
Operating,
|
||||||
@@ -22,6 +24,11 @@ pub struct CsvAllocationStatistic {
|
|||||||
account_ranges: String,
|
account_ranges: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AllocationStatisticAccountRange {
|
||||||
|
start: String,
|
||||||
|
end: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct CsvAccount {
|
pub struct CsvAccount {
|
||||||
#[serde(rename = "Code")]
|
#[serde(rename = "Code")]
|
||||||
@@ -96,30 +103,138 @@ where
|
|||||||
CostCentre: Read,
|
CostCentre: Read,
|
||||||
Output: std::io::Write,
|
Output: std::io::Write,
|
||||||
{
|
{
|
||||||
let mut accounts_reader = accounts;
|
let mut lines_reader = lines;
|
||||||
let all_accounts_sorted: Result<Vec<CsvAccount>, csv::Error> =
|
let lines = lines_reader
|
||||||
accounts_reader.deserialize::<CsvAccount>().collect();
|
.deserialize()
|
||||||
let mut accounts_sorted = all_accounts_sorted?;
|
.collect::<Result<Vec<CsvCost>, csv::Error>>()?;
|
||||||
|
|
||||||
// Sort the accounts, as allocation statistics use account ranges
|
// Sort the accounts, as allocation statistics use account ranges
|
||||||
if use_numeric_accounts {
|
let all_accounts_sorted: Vec<String> = if use_numeric_accounts {
|
||||||
accounts_sorted.sort_by(|a, b| {
|
lines
|
||||||
a.code
|
.iter()
|
||||||
.parse::<i32>()
|
.map(|line| line.account.clone().parse::<i32>().unwrap())
|
||||||
.unwrap()
|
.unique()
|
||||||
.cmp(&b.code.parse::<i32>().unwrap())
|
.sorted()
|
||||||
})
|
.map(|account| account.to_string())
|
||||||
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
accounts_sorted.sort_by(|a, b| a.code.cmp(&b.code))
|
lines
|
||||||
|
.iter()
|
||||||
|
.map(|line| line.account.clone())
|
||||||
|
.unique()
|
||||||
|
.sorted()
|
||||||
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build out the the list of allocation rules from areas/allocation statistics (similar to ppm building 'cost drivers')
|
let mut allocation_statistics_reader = allocation_statistics;
|
||||||
|
let allocation_statistics = allocation_statistics_reader
|
||||||
|
.deserialize()
|
||||||
|
.collect::<Result<Vec<CsvAllocationStatistic>, csv::Error>>()?;
|
||||||
|
|
||||||
|
// For each allocation statistic, sum the cost centres across accounts in the allocaiton statistic range
|
||||||
|
let flat_department_costs: Vec<(String, String, f64)> = allocation_statistics
|
||||||
|
.iter()
|
||||||
|
.map(|allocation_statistic| {
|
||||||
|
(
|
||||||
|
allocation_statistic,
|
||||||
|
split_allocation_statistic_range(allocation_statistic),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.flat_map(|allocation_statistic| {
|
||||||
|
let mut total_department_costs: HashMap<String, f64> = HashMap::new();
|
||||||
|
let cc_costs = lines
|
||||||
|
.iter()
|
||||||
|
.filter(|line| {
|
||||||
|
let line_index = all_accounts_sorted
|
||||||
|
.iter()
|
||||||
|
.position(|account| account == &line.account);
|
||||||
|
allocation_statistic
|
||||||
|
.1
|
||||||
|
.iter()
|
||||||
|
.find(|range| {
|
||||||
|
let start_index = all_accounts_sorted
|
||||||
|
.iter()
|
||||||
|
.position(|account| account == range.0);
|
||||||
|
let end_index = all_accounts_sorted
|
||||||
|
.iter()
|
||||||
|
.position(|account| account == range.1);
|
||||||
|
line_index >= start_index && line_index <= end_index
|
||||||
|
})
|
||||||
|
.is_some()
|
||||||
|
})
|
||||||
|
.for_each(|line| {
|
||||||
|
*total_department_costs.entry(line.department).or_insert(0.) += line.value;
|
||||||
|
});
|
||||||
|
total_department_costs
|
||||||
|
.iter()
|
||||||
|
.map(|entry| (entry.0, allocation_statistic.0.name, entry.1))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
// TODO: If ignore negative is used, then set values < 0 to 0
|
||||||
|
|
||||||
|
let mut rollups: HashMap<String, HashMap<String, Vec<String>>> = HashMap::new();
|
||||||
|
let mut cost_centres = cost_centres;
|
||||||
|
for cost_centre in cost_centres.records() {
|
||||||
|
let cost_centre = cost_centre?;
|
||||||
|
// Extract rollups, used later with the areas... I could do a map of rollups -> cc's, or just a list of rollups on each cc struct
|
||||||
|
// I think map of rollup -> cc would be better, although this would need to be for each rollup slot... so a map of maps?
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut areas = areas;
|
||||||
|
let area_name_index = areas
|
||||||
|
.headers()?
|
||||||
|
.iter()
|
||||||
|
.position(|header| header == "Name")
|
||||||
|
.unwrap();
|
||||||
|
let allocation_statistic_index = areas
|
||||||
|
.headers()?
|
||||||
|
.iter()
|
||||||
|
.position(|header| header == "AllocationStatistic")
|
||||||
|
.unwrap();
|
||||||
|
// For each overhead area, get the cost centres in the area, and get all cost centres
|
||||||
|
// that fit the limit to criteria for the area (skip any cases of overhead cc = other cc).
|
||||||
|
// Then get the totals for the other ccs, by looking in the flat_department_costs, where the
|
||||||
|
// allocation statistic matches the allocation statistic for this area
|
||||||
|
for area in areas.records() {
|
||||||
|
let area = area?;
|
||||||
|
// Check for limitTos, should probably somehow build out the list of allocation rules from this point.
|
||||||
|
let area_name = area.get(area_name_index).unwrap();
|
||||||
|
let allocation_statistic = area.get(allocation_statistic_index).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, for each cc match total produced previously, sum the overhead cc where overhead cc appears in other cc, then
|
||||||
|
// divide the other cc by this summed amount
|
||||||
|
|
||||||
// do reciprocal allocation (only for variable portion of accounts), for each account
|
// do reciprocal allocation (only for variable portion of accounts), for each account
|
||||||
|
|
||||||
// Copy across fixed stuff (if necessary, not sure it is)
|
// Copy across fixed stuff (if necessary, not sure it is)... don't think it's necessary, initial totals handle this
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn split_allocation_statistic_range(
|
||||||
|
allocation_statistic: &CsvAllocationStatistic,
|
||||||
|
) -> Vec<AllocationStatisticAccountRange> {
|
||||||
|
// TODO: This split needs to be more comprehensive so that we don't split between quotes
|
||||||
|
let split = allocation_statistic.account_ranges.split(";");
|
||||||
|
split
|
||||||
|
.map(|split| {
|
||||||
|
let range_split = split.split('-').collect::<Vec<_>>();
|
||||||
|
if range_split.len() == 1 {
|
||||||
|
AllocationStatisticAccountRange {
|
||||||
|
start: range_split[0].to_owned(),
|
||||||
|
end: range_split[0].to_owned(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AllocationStatisticAccountRange {
|
||||||
|
start: range_split[0].to_owned(),
|
||||||
|
end: range_split[1].to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
// Perform the reciprocal allocation (matrix) method to allocate servicing departments (indirect) costs
|
// Perform the reciprocal allocation (matrix) method to allocate servicing departments (indirect) costs
|
||||||
// to functional departments. Basically just a matrix solve, uses regression (moore-penrose pseudoinverse) when
|
// to functional departments. Basically just a matrix solve, uses regression (moore-penrose pseudoinverse) when
|
||||||
// matrix is singular
|
// matrix is singular
|
||||||
|
|||||||
Reference in New Issue
Block a user