use super::funejson::Value; use super::money_amount::{money, money_from_value, money_to_value, Money}; use super::money_apply_rate::apply_rate; /// Net total for one invoice line. /// /// The discount is rounded once, here, before any tax is applied. Rounding the /// discount and the tax in the other order produces answers that differ by a /// penny on roughly one line in five. /// /// # Panics /// Panics if the discount is outside 0..=10000 basis points. pub fn line_total(unit_price: &Money, quantity: i64, discount_basis_points: i64) -> Money { if !(0..=10000).contains(&discount_basis_points) { panic!( "discount_basis_points must be between 0 and 10000, received {}", discount_basis_points ); } let gross = money(unit_price.minor * quantity, &unit_price.currency); let discount = apply_rate(&gross, discount_basis_points, "half-up"); money(gross.minor - discount.minor, &gross.currency) } pub fn fune_vector(args: &[Value]) -> Value { // Refuse what the typed signature cannot hold, with the wording TypeScript // and Python use, rather than let the conversion below quietly change it. if let Value::Float(f) = args[1] { if f.fract() != 0.0 { panic!("quantity must be an integer, received {}", f); } } if let Value::Float(f) = args[2] { if f.fract() != 0.0 { panic!("discountBasisPoints must be between 0 and 10000, received {}", f); } } money_to_value(&line_total( &money_from_value(&args[0]), args[1].as_i64(), args[2].as_i64(), )) }