lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

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 }