Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::manufacturing_bom_explode::{bom_line_from_value, explode_bom, BomLine}; ← from manufacturing.bom-explode ^1.0.0 · built alongside by fune
use super::math_rational::{add_rational, multiply_rational, rational, rational_to_integer, Rational}; ← from math.rational ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value}; ← from money.amount ^1.0.0 · built alongside by fune
/// Standard cost of one unit, by cost element, rolled up through the BOM.
/// Every element is an exact fraction of a minor unit until it is rounded once.
///
/// # Panics
/// Panics on anything `explode_bom` refuses, a missing, duplicate or negative
/// material cost, a cost for a made item, negative operation time or rates,
/// or a cost or rate in another currency.
pub fn bom_cost(
lines: &[BomLine],
item: &str,
material_costs: &[ItemCost],
operations: &[RoutingOperation],
currency: &str,
mode: &str,
) -> CostRollup {
let zero = money(0, currency);
let mut needs: Vec<(String, Rational, bool)> = vec![(item.to_string(), rational(1, 1), false)];
for r in explode_bom(lines, item, 1) {
needs.push((r.item, r.quantity, r.leaf));
}
let mut costs: Vec<(&str, i64)> = Vec::new();
for c in material_costs {
assert_same_currency(&zero, &c.unit_cost);
if costs.iter().any(|(n, _)| *n == c.item) {
panic!("duplicate material cost for \"{}\"", c.item);
}
if c.unit_cost.minor < 0 {
panic!("unitCost of \"{}\" must not be negative, received {}", c.item, c.unit_cost.minor);
}
costs.push((&c.item, c.unit_cost.minor));
}
let mut material = rational(0, 1);
for (name, quantity, leaf) in &needs {
let cost = costs.iter().find(|(n, _)| n == name).map(|(_, c)| *c);
if !leaf {
if cost.is_some() {
panic!(
"\"{}\" is made from its own bill of materials, so its cost is rolled up; remove it from materialCosts",
name
);
}
continue;
}
let cost = match cost {
Some(c) => c,
None => panic!("no material cost for \"{}\"", name),
};
material = add_rational(&material, &multiply_rational(quantity, &rational(cost, 1)));
}
let mut labour = rational(0, 1);
let mut overhead = rational(0, 1);
for op in operations {
if op.seconds < 0 || op.seconds > (1i64 << 53) - 1 {
panic!(
"seconds of an operation on \"{}\" must be a whole number, not negative, received {}",
op.item, op.seconds
);
}
assert_same_currency(&zero, &op.labour_rate);
assert_same_currency(&zero, &op.overhead_rate);
if op.labour_rate.minor < 0 || op.overhead_rate.minor < 0 {
panic!("the rates of an operation on \"{}\" must not be negative", op.item);
}
let need = match needs.iter().find(|(n, _, _)| *n == op.item) {
Some((_, q, _)) => *q,
None => continue,
};
let hours = multiply_rational(&need, &rational(op.seconds, 3600));
labour = add_rational(&labour, &multiply_rational(&hours, &rational(op.labour_rate.minor, 1)));
overhead = add_rational(&overhead, &multiply_rational(&hours, &rational(op.overhead_rate.minor, 1)));
}
let m = rational_to_integer(&material, mode);
let l = rational_to_integer(&labour, mode);
let o = rational_to_integer(&overhead, mode);
CostRollup {
material: money(m, currency),
labour: money(l, currency),
overhead: money(o, currency),
total: money(m + l + o, currency),
}
}
pub fn cost_rollup_to_value(c: &CostRollup) -> Value {
Value::obj(vec![
("material", money_to_value(&c.material)),
("labour", money_to_value(&c.labour)),
("overhead", money_to_value(&c.overhead)),
("total", money_to_value(&c.total)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let lines: Vec<BomLine> = args[0].as_arr().iter().map(bom_line_from_value).collect();
let costs: Vec<ItemCost> = args[2]
.as_arr()
.iter()
.map(|v| ItemCost {
item: v.get("item").as_str().to_string(),
unit_cost: money_from_value(v.get("unitCost")),
})
.collect();
let operations: Vec<RoutingOperation> = args[3]
.as_arr()
.iter()
.map(|v| {
if let Value::Float(f) = v.get("seconds") {
panic!(
"seconds of an operation on \"{}\" must be a whole number, not negative, received {}",
v.get("item").as_str(),
f
);
}
RoutingOperation {
item: v.get("item").as_str().to_string(),
seconds: v.get("seconds").as_i64(),
labour_rate: money_from_value(v.get("labourRate")),
overhead_rate: money_from_value(v.get("overheadRate")),
}
})
.collect();
cost_rollup_to_value(&bom_cost(&lines, args[1].as_str(), &costs, &operations, args[4].as_str(), args[5].as_str()))
}