use rust_decimal::Decimal;
#[must_use]
pub fn extract_per_unit_price<T>(
units_number: Decimal,
annotation: Option<(bool, Decimal, T)>,
cost: Option<(Option<Decimal>, Option<Decimal>, T)>,
) -> Option<(Decimal, T)> {
if let Some((is_total, amount, currency)) = annotation {
if is_total {
if !units_number.is_zero() {
return Some((amount / units_number.abs(), currency));
}
} else {
return Some((amount, currency));
}
}
if let Some((per, total, currency)) = cost {
if let Some(per) = per {
return Some((per, currency));
}
if let Some(total) = total
&& !units_number.is_zero()
{
return Some((total / units_number.abs(), currency));
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn unit_annotation_returns_amount_directly() {
let p = extract_per_unit_price(dec!(5), Some((false, dec!(1.40), "EUR")), None);
assert_eq!(p, Some((dec!(1.40), "EUR")));
}
#[test]
fn total_annotation_divides_by_unit_count() {
let p = extract_per_unit_price(dec!(10), Some((true, dec!(1500), "USD")), None);
assert_eq!(p, Some((dec!(150), "USD")));
}
#[test]
fn total_annotation_uses_abs_unit_count() {
let p = extract_per_unit_price(dec!(-27204.53), Some((true, dec!(15152.07), "EUR")), None);
let expected = dec!(15152.07) / dec!(27204.53);
assert_eq!(p, Some((expected, "EUR")));
assert!(p.unwrap().0 > dec!(0.55) && p.unwrap().0 < dec!(0.56));
}
#[test]
fn total_annotation_with_zero_units_falls_through_to_cost() {
let p = extract_per_unit_price(
dec!(0),
Some((true, dec!(100), "EUR")),
Some((Some(dec!(50)), None, "USD")),
);
assert_eq!(p, Some((dec!(50), "USD")));
}
#[test]
fn total_annotation_with_zero_units_and_no_cost_returns_none() {
let p = extract_per_unit_price::<&str>(dec!(0), Some((true, dec!(100), "EUR")), None);
assert_eq!(p, None);
}
#[test]
fn cost_per_unit_used_when_no_annotation() {
let p = extract_per_unit_price(dec!(10), None, Some((Some(dec!(50.00)), None, "USD")));
assert_eq!(p, Some((dec!(50.00), "USD")));
}
#[test]
fn cost_total_divides_by_unit_count() {
let p = extract_per_unit_price(dec!(10), None, Some((None, Some(dec!(500)), "USD")));
assert_eq!(p, Some((dec!(50), "USD")));
}
#[test]
fn cost_total_with_zero_units_returns_none() {
let p = extract_per_unit_price::<&str>(dec!(0), None, Some((None, Some(dec!(500)), "USD")));
assert_eq!(p, None);
}
#[test]
fn annotation_wins_over_cost_when_both_present() {
let p = extract_per_unit_price(
dec!(5),
Some((false, dec!(1.40), "EUR")),
Some((Some(dec!(1.25)), None, "EUR")),
);
assert_eq!(p, Some((dec!(1.40), "EUR")));
}
#[test]
fn total_annotation_wins_over_cost_per_unit() {
let p = extract_per_unit_price(
dec!(-10),
Some((true, dec!(14), "EUR")),
Some((Some(dec!(1.25)), None, "EUR")),
);
assert_eq!(p, Some((dec!(1.4), "EUR")));
}
#[test]
fn cost_per_wins_over_cost_total_when_both_present() {
let p = extract_per_unit_price(
dec!(10),
None,
Some((Some(dec!(50)), Some(dec!(999)), "USD")),
);
assert_eq!(p, Some((dec!(50), "USD")));
}
#[test]
fn no_inputs_returns_none() {
let p = extract_per_unit_price::<&str>(dec!(10), None, None);
assert_eq!(p, None);
}
#[test]
fn annotation_without_amount_falls_through_to_cost() {
let p = extract_per_unit_price(dec!(10), None, Some((Some(dec!(7)), None, "USD")));
assert_eq!(p, Some((dec!(7), "USD")));
}
}