quantity_price.rs (6247B)
1 use crate::{RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreUnit}; 2 3 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 4 #[derive(Clone, Debug, PartialEq, Eq)] 5 pub struct RadrootsCoreQuantityPrice { 6 #[cfg_attr(feature = "serde", serde(alias = "money", alias = "price"))] 7 pub amount: RadrootsCoreMoney, 8 #[cfg_attr(feature = "serde", serde(alias = "per", alias = "quantity"))] 9 pub quantity: RadrootsCoreQuantity, 10 } 11 12 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 13 pub enum RadrootsCoreQuantityPriceError { 14 PerQuantityZero, 15 UnitMismatch { 16 have: RadrootsCoreUnit, 17 want: RadrootsCoreUnit, 18 }, 19 NonConvertibleUnits { 20 from: RadrootsCoreUnit, 21 to: RadrootsCoreUnit, 22 }, 23 } 24 25 pub trait RadrootsCoreQuantityPriceOps { 26 #[must_use] 27 fn cost_for(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney; 28 29 #[must_use] 30 fn cost_for_rounded(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney; 31 32 #[must_use] 33 fn cost_for_with_quantized_price(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney; 34 35 fn try_cost_for( 36 &self, 37 qty: &RadrootsCoreQuantity, 38 ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError>; 39 40 fn try_cost_for_rounded( 41 &self, 42 qty: &RadrootsCoreQuantity, 43 ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError>; 44 } 45 46 impl RadrootsCoreQuantityPrice { 47 #[inline] 48 pub fn new(amount: RadrootsCoreMoney, quantity: RadrootsCoreQuantity) -> Self { 49 Self { amount, quantity } 50 } 51 52 #[inline] 53 pub fn try_cost_for_amount_in( 54 &self, 55 amount: RadrootsCoreDecimal, 56 unit: RadrootsCoreUnit, 57 ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { 58 use crate::unit::convert_unit_decimal; 59 60 let target = self.quantity.unit; 61 62 let normalized = if unit == target { 63 amount 64 } else { 65 convert_unit_decimal(amount, unit, target).map_err(|_| { 66 RadrootsCoreQuantityPriceError::NonConvertibleUnits { 67 from: unit, 68 to: target, 69 } 70 })? 71 }; 72 73 let qty = RadrootsCoreQuantity::new(normalized, target); 74 self.try_cost_for_rounded(&qty) 75 } 76 77 #[inline] 78 pub fn try_cost_for_quantity_in( 79 &self, 80 qty: &RadrootsCoreQuantity, 81 ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { 82 self.try_cost_for_amount_in(qty.amount, qty.unit) 83 } 84 85 #[inline] 86 pub fn is_price_per_canonical_unit(&self) -> bool { 87 self.quantity.unit == self.quantity.unit.canonical_unit() 88 && self.quantity.amount == RadrootsCoreDecimal::ONE 89 } 90 91 #[inline] 92 pub fn try_to_unit_price( 93 &self, 94 unit: RadrootsCoreUnit, 95 ) -> Result<RadrootsCoreQuantityPrice, RadrootsCoreQuantityPriceError> { 96 use crate::unit::convert_unit_decimal; 97 98 if self.quantity.amount.is_zero() { 99 return Err(RadrootsCoreQuantityPriceError::PerQuantityZero); 100 } 101 102 let normalized = if self.quantity.unit == unit { 103 self.quantity.amount 104 } else { 105 convert_unit_decimal(self.quantity.amount, self.quantity.unit, unit).map_err(|_| { 106 RadrootsCoreQuantityPriceError::NonConvertibleUnits { 107 from: self.quantity.unit, 108 to: unit, 109 } 110 })? 111 }; 112 113 if normalized.is_zero() { 114 return Err(RadrootsCoreQuantityPriceError::PerQuantityZero); 115 } 116 117 let amount = self.amount.div_decimal(normalized); 118 Ok(RadrootsCoreQuantityPrice { 119 amount, 120 quantity: RadrootsCoreQuantity::new(RadrootsCoreDecimal::ONE, unit), 121 }) 122 } 123 124 #[inline] 125 pub fn try_to_canonical_unit_price( 126 &self, 127 ) -> Result<RadrootsCoreQuantityPrice, RadrootsCoreQuantityPriceError> { 128 self.try_to_unit_price(self.quantity.unit.canonical_unit()) 129 } 130 } 131 132 impl RadrootsCoreQuantityPriceOps for RadrootsCoreQuantityPrice { 133 #[inline] 134 fn cost_for(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney { 135 if qty.amount.is_zero() { 136 return RadrootsCoreMoney::zero(self.amount.currency); 137 } 138 if self.quantity.amount.is_zero() { 139 return RadrootsCoreMoney::zero(self.amount.currency); 140 } 141 if qty.unit != self.quantity.unit { 142 return RadrootsCoreMoney::zero(self.amount.currency); 143 } 144 145 let ratio = qty.amount / self.quantity.amount; 146 self.amount.mul_decimal(ratio) 147 } 148 149 #[inline] 150 fn cost_for_rounded(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney { 151 self.cost_for(qty).quantize_to_currency() 152 } 153 154 #[inline] 155 fn cost_for_with_quantized_price(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney { 156 if qty.amount.is_zero() { 157 return RadrootsCoreMoney::zero(self.amount.currency); 158 } 159 if self.quantity.amount.is_zero() { 160 return RadrootsCoreMoney::zero(self.amount.currency); 161 } 162 if qty.unit != self.quantity.unit { 163 return RadrootsCoreMoney::zero(self.amount.currency); 164 } 165 let unit_price_q = self.amount.clone().quantize_to_currency(); 166 unit_price_q.mul_decimal(qty.amount / self.quantity.amount) 167 } 168 169 #[inline] 170 fn try_cost_for( 171 &self, 172 qty: &RadrootsCoreQuantity, 173 ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { 174 if self.quantity.amount.is_zero() { 175 return Err(RadrootsCoreQuantityPriceError::PerQuantityZero); 176 } 177 if qty.unit != self.quantity.unit { 178 return Err(RadrootsCoreQuantityPriceError::UnitMismatch { 179 have: qty.unit, 180 want: self.quantity.unit, 181 }); 182 } 183 let ratio = qty.amount / self.quantity.amount; 184 Ok(self.amount.mul_decimal(ratio)) 185 } 186 187 #[inline] 188 fn try_cost_for_rounded( 189 &self, 190 qty: &RadrootsCoreQuantity, 191 ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { 192 Ok(self.try_cost_for(qty)?.quantize_to_currency()) 193 } 194 }