1use crate::currency::Currency;
2use crate::decimal::{self, Decimal, RoundingStrategy};
3use crate::error::MoneyError;
4use crate::exact::{
5 CurrencyAmount, canonical_amount_format, checked_add_amounts, checked_div_decimal,
6 checked_mul_decimal, checked_sub_amounts, copy_decimal, decimal_from_scaled_units,
7 parse_canonical_decimal, round_to_money,
8};
9use crate::money::Money;
10use serde::{Deserialize, Serialize};
11use std::fmt;
12use std::hash::{Hash, Hasher};
13
14#[cfg(feature = "dataframe")]
15use df_derive_macros::ToDataFrame;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[cfg_attr(feature = "dataframe", derive(ToDataFrame))]
26pub struct MonetaryAmount {
27 #[serde(with = "paft_decimal::serde::canonical_str")]
28 amount: Decimal,
29 #[cfg_attr(feature = "dataframe", df_derive(as_str))]
30 currency: Currency,
31}
32
33impl MonetaryAmount {
34 #[must_use]
36 pub const fn new(amount: Decimal, currency: Currency) -> Self {
37 Self { amount, currency }
38 }
39
40 pub fn from_canonical_str(amount: &str, currency: Currency) -> Result<Self, MoneyError> {
46 let decimal = parse_canonical_decimal(amount)?;
47 Ok(Self::new(decimal, currency))
48 }
49
50 pub fn from_scaled_units(
60 units: i128,
61 scale: u32,
62 currency: Currency,
63 ) -> Result<Self, MoneyError> {
64 Ok(Self::new(
65 decimal_from_scaled_units(units, scale)?,
66 currency,
67 ))
68 }
69
70 #[must_use]
72 pub fn zero(currency: Currency) -> Self {
73 Self::new(decimal::zero(), currency)
74 }
75
76 #[must_use]
78 pub fn amount(&self) -> Decimal {
79 copy_decimal(&self.amount)
80 }
81
82 #[must_use]
84 pub const fn currency(&self) -> &Currency {
85 &self.currency
86 }
87
88 #[must_use]
90 pub fn format(&self) -> String {
91 canonical_amount_format(self)
92 }
93
94 pub fn try_add(&self, rhs: &Self) -> Result<Self, MoneyError> {
101 let amount = checked_add_amounts(self, rhs)?;
102 Ok(Self::new(amount, self.currency.clone()))
103 }
104
105 pub fn try_sub(&self, rhs: &Self) -> Result<Self, MoneyError> {
112 let amount = checked_sub_amounts(self, rhs)?;
113 Ok(Self::new(amount, self.currency.clone()))
114 }
115
116 pub fn try_mul(&self, factor: &Decimal) -> Result<Self, MoneyError> {
122 let amount = checked_mul_decimal(&self.amount, factor)?;
123 Ok(Self::new(amount, self.currency.clone()))
124 }
125
126 pub fn try_div(&self, divisor: &Decimal) -> Result<Self, MoneyError> {
133 let amount = checked_div_decimal(&self.amount, divisor)?;
134 Ok(Self::new(amount, self.currency.clone()))
135 }
136
137 pub fn to_money(&self) -> Result<Money, MoneyError> {
144 self.to_money_with(RoundingStrategy::MidpointAwayFromZero, None)
145 }
146
147 pub fn to_money_with(
155 &self,
156 rounding: RoundingStrategy,
157 target_fraction_digits: Option<u32>,
158 ) -> Result<Money, MoneyError> {
159 round_to_money(
160 &self.amount,
161 self.currency.clone(),
162 rounding,
163 target_fraction_digits,
164 )
165 }
166}
167
168impl CurrencyAmount for MonetaryAmount {
169 fn raw_amount(&self) -> &Decimal {
170 &self.amount
171 }
172
173 fn raw_currency(&self) -> &Currency {
174 &self.currency
175 }
176}
177
178impl Hash for MonetaryAmount {
179 fn hash<H: Hasher>(&self, state: &mut H) {
180 self.currency.hash(state);
181 decimal::to_canonical_string(&self.amount).hash(state);
182 }
183}
184
185impl fmt::Display for MonetaryAmount {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 f.write_str(&self.format())
188 }
189}
190
191impl From<Money> for MonetaryAmount {
192 fn from(money: Money) -> Self {
193 Self::new(money.amount(), money.currency().clone())
194 }
195}