use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value, Money}; fn not_negative(name: &str, amount: &Money) { if amount.minor < 0 { panic!("{} must not be negative, received {}", name, amount.minor); } } /// Turns, days inventory outstanding and days of cover. Each is one exact /// division rounded half-up, then made a float, so every language agrees. /// /// # Panics /// Panics on mixed currencies, a negative amount, or a period under one day. pub fn stock_turnover(opening_stock: &Money, closing_stock: &Money, cost_of_sales: &Money, period_days: i64) -> StockTurnover { assert_same_currency(opening_stock, closing_stock); assert_same_currency(opening_stock, cost_of_sales); not_negative("openingStock", opening_stock); not_negative("closingStock", closing_stock); not_negative("costOfSales", cost_of_sales); if period_days < 1 { panic!("periodDays must be a whole number of days, at least 1, received {}", period_days); } let both = opening_stock.minor + closing_stock.minor; let cogs = cost_of_sales.minor; StockTurnover { average_stock: money(round_div(both, 2, "half-up"), &opening_stock.currency), turns: if both == 0 { None } else { Some(round_div(cogs * 200, both, "half-up") as f64 / 100.0) }, days_inventory_outstanding: if cogs == 0 { None } else { Some(round_div(both * period_days * 10, cogs * 2, "half-up") as f64 / 10.0) }, days_of_cover: if cogs == 0 { None } else { Some(round_div(closing_stock.minor * period_days * 10, cogs, "half-up") as f64 / 10.0) }, } } fn optional_float(value: Option) -> Value { match value { Some(v) => Value::Float(v), None => Value::Null, } } pub fn stock_turnover_to_value(result: &StockTurnover) -> Value { Value::obj(vec![ ("averageStock", money_to_value(&result.average_stock)), ("turns", optional_float(result.turns)), ("daysInventoryOutstanding", optional_float(result.days_inventory_outstanding)), ("daysOfCover", optional_float(result.days_of_cover)), ]) } pub fn fune_vector(args: &[Value]) -> Value { if let Value::Float(f) = &args[3] { panic!("periodDays must be a whole number of days, at least 1, received {}", f); } stock_turnover_to_value(&stock_turnover( &money_from_value(&args[0]), &money_from_value(&args[1]), &money_from_value(&args[2]), args[3].as_i64(), )) }