use super::funejson::Value; use super::finance_margin::margin; use super::finance_tax_remove_vat::remove_vat; use super::money_amount::{money_from_value, money_to_value, Money}; /// Gross profit on a menu price. /// /// Hospitality GP is worked out on the price without VAT, because the VAT is /// the tax authority's money, not the kitchen's: a 14.50 dish costing 3.50 at /// 20% VAT is a 71.03% GP, not the 75.86% the VAT-inclusive price suggests. /// /// # Panics /// Panics on a negative cost or price, mixed currencies, or no VAT rule. pub fn menu_gross_profit(cost: &Money, menu_price: &Money, jurisdiction: &str, category: &str, on_date: &str) -> MenuGrossProfit { if cost.minor < 0 { panic!("cost must not be negative, received {}", cost.minor); } if menu_price.minor < 0 { panic!("menuPrice must not be negative, received {}", menu_price.minor); } let vat = remove_vat(menu_price, jurisdiction, category, on_date); let breakdown = margin(cost, &vat.net); let gp = breakdown.margin_basis_points; MenuGrossProfit { menu_price: menu_price.clone(), vat: vat.tax.clone(), net_price: vat.net.clone(), cost: cost.clone(), gross_profit: breakdown.profit, gp_basis_points: gp, cost_basis_points: gp.map(|g| 10000 - g), vat_rate_basis_points: vat.basis_points, } } fn opt_int(v: Option) -> Value { match v { Some(i) => Value::Int(i), None => Value::Null, } } pub fn menu_gross_profit_to_value(g: &MenuGrossProfit) -> Value { Value::obj(vec![ ("menuPrice", money_to_value(&g.menu_price)), ("vat", money_to_value(&g.vat)), ("netPrice", money_to_value(&g.net_price)), ("cost", money_to_value(&g.cost)), ("grossProfit", money_to_value(&g.gross_profit)), ("gpBasisPoints", opt_int(g.gp_basis_points)), ("costBasisPoints", opt_int(g.cost_basis_points)), ("vatRateBasisPoints", Value::Int(g.vat_rate_basis_points)), ]) } pub fn fune_vector(args: &[Value]) -> Value { menu_gross_profit_to_value(&menu_gross_profit( &money_from_value(&args[0]), &money_from_value(&args[1]), args[2].as_str(), args[3].as_str(), args[4].as_str(), )) }