grid_tariffs/
money.rs

1use std::{
2    fmt::{Display, Formatter},
3    iter::Sum,
4    ops::{Add, Div, Mul, Neg, Sub},
5    str::FromStr,
6};
7
8use serde::Serialize;
9
10use crate::{Country, currency::Currency};
11
12#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
13pub enum MoneyError {
14    #[error("Invalid money")]
15    InvalidMoney,
16    #[error("Money sub-unit is more than 99")]
17    SubunitOutOfRange,
18}
19
20#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize)]
21#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
22pub struct Money(i64);
23
24impl Money {
25    pub const ZERO: Self = Self(0);
26    const GAIN: i64 = 100000;
27    const GAIN_F64: f64 = 100000.;
28
29    pub const fn new(int: i64, fract: u8) -> Self {
30        let base = int * Self::GAIN;
31        let sub = fract as i64 * Self::GAIN / 100;
32        Self(if base.is_negative() {
33            base - sub
34        } else {
35            base + sub
36        })
37    }
38
39    /// From a subunit amount (öre, cents etc)
40    /// E.g. Money::new_subunit(8.86) -> Money(8860)
41    pub const fn new_subunit(value: f64) -> Self {
42        Self((value * Self::GAIN_F64 / 100.) as i64)
43    }
44
45    pub const fn display(&self, currency: Currency) -> MoneyDisplay {
46        MoneyDisplay(*self, currency)
47    }
48
49    pub const fn inner(&self) -> i64 {
50        self.0
51    }
52
53    pub const fn from_inner(inner: i64) -> Self {
54        Self(inner)
55    }
56
57    pub const fn from_f64(value: f64) -> Self {
58        Self((value * Self::GAIN_F64) as i64)
59    }
60
61    pub const fn to_f64(self) -> f64 {
62        self.0 as f64 / Self::GAIN_F64
63    }
64
65    pub const fn divide_by(&self, by: i64) -> Self {
66        Self(self.0 / by)
67    }
68
69    pub(crate) const fn add_vat(&self, country: Country) -> Money {
70        let rate = match country {
71            Country::SE => 1.25,
72        };
73        Self::from_f64(self.to_f64() * rate)
74    }
75}
76
77impl Display for Money {
78    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
79        format!("{:.2}", self.to_f64()).fmt(f)
80    }
81}
82
83impl FromStr for Money {
84    type Err = MoneyError;
85
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        if let Some((int, fract)) = s.split_once('.') {
88            let int: i64 = int.parse().map_err(|_| MoneyError::InvalidMoney)?;
89            let fract: u8 = fract.parse().map_err(|_| MoneyError::SubunitOutOfRange)?;
90            Ok(Self::new(int, fract))
91        } else {
92            let int: i64 = s.parse().map_err(|_| MoneyError::InvalidMoney)?;
93            Ok(Self::new(int, 0))
94        }
95    }
96}
97
98impl From<f64> for Money {
99    fn from(value: f64) -> Self {
100        Self::from_f64(value)
101    }
102}
103
104impl From<(i32, u8)> for Money {
105    fn from((int, fract): (i32, u8)) -> Self {
106        Self::new(int.into(), fract)
107    }
108}
109
110impl From<Money> for f64 {
111    fn from(value: Money) -> Self {
112        value.to_f64()
113    }
114}
115
116impl Mul<f64> for Money {
117    type Output = Self;
118
119    fn mul(self, rhs: f64) -> Self::Output {
120        Self::from(self.to_f64() * rhs)
121    }
122}
123
124impl Div<f64> for Money {
125    type Output = Self;
126
127    fn div(self, rhs: f64) -> Self::Output {
128        Self::from(self.to_f64() / rhs)
129    }
130}
131
132impl Div<i64> for Money {
133    type Output = Self;
134
135    fn div(self, rhs: i64) -> Self::Output {
136        Self(self.0 / rhs)
137    }
138}
139
140impl Add for Money {
141    type Output = Self;
142
143    fn add(self, rhs: Self) -> Self::Output {
144        Self(self.0 + rhs.0)
145    }
146}
147
148impl Sub for Money {
149    type Output = Self;
150
151    fn sub(self, rhs: Self) -> Self::Output {
152        Self(self.0 - rhs.0)
153    }
154}
155
156impl Neg for Money {
157    type Output = Self;
158
159    fn neg(self) -> Self::Output {
160        Self(-self.0)
161    }
162}
163
164impl Sum for Money {
165    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
166        iter.reduce(|a, b| a + b).unwrap_or_default()
167    }
168}
169
170impl<'a> Sum<&'a Money> for Money {
171    fn sum<I: Iterator<Item = &'a Money>>(iter: I) -> Self {
172        iter.map(|m| m.to_owned()).sum()
173    }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
177pub struct MoneyDisplay(pub Money, pub Currency);
178
179impl std::fmt::Display for MoneyDisplay {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        f.write_fmt(format_args!("{} {}", self.amount(), self.currency().sign()))
182    }
183}
184
185impl MoneyDisplay {
186    pub const fn money(&self) -> Money {
187        self.0
188    }
189    pub const fn currency(&self) -> Currency {
190        self.1
191    }
192
193    pub fn subunit_display(&self) -> String {
194        format!(
195            "{} {}",
196            self.subunit_amount(),
197            self.currency().subunit_sign()
198        )
199    }
200
201    pub fn amount(&self) -> String {
202        format!("{:.2}", self.money().to_f64())
203    }
204
205    pub fn subunit_amount(&self) -> String {
206        format!("{:.0}", self.money().to_f64() * 100.)
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::{Currency, Money};
213
214    #[test]
215    fn new_money_positive() {
216        assert_eq!(Money::new(2, 14), Money(214000));
217    }
218
219    #[test]
220    fn new_money_small() {
221        assert_eq!(Money::new(0, 4), Money(4000));
222    }
223
224    #[test]
225    fn new_money_negative() {
226        assert_eq!(Money::new(-2, 14), Money(-214000));
227    }
228
229    #[test]
230    fn from_negative_f64() {
231        assert_eq!(Money::from(-2.14), Money(-214000));
232    }
233
234    #[test]
235    fn from_small_f64() {
236        assert_eq!(Money::from(0.04732), Money(4732));
237    }
238
239    #[test]
240    fn new_subunit() {
241        assert_eq!(Money::new_subunit(14.), Money(14_000));
242        assert_eq!(Money::new_subunit(8.86), Money(8_860));
243    }
244
245    #[test]
246    fn new_subunit_and_new_equal() {
247        assert_eq!(Money::new_subunit(14.), Money::new(0, 14));
248    }
249
250    #[test]
251    fn display_impl() {
252        assert_eq!(Money(9000).to_string(), "0.09");
253        assert_eq!(Money(199999).to_string(), "2.00");
254        assert_eq!(Money(20_999_999).to_string(), "210.00");
255    }
256
257    #[test]
258    fn display() {
259        let m = Money::from((1, 42)).display(Currency::SEK);
260        assert_eq!(m.to_string(), "1.42 kr");
261    }
262
263    #[test]
264    fn display_small() {
265        let m = Money::from((0, 9)).display(Currency::SEK);
266        assert_eq!(m.to_string(), "0.09 kr");
267    }
268
269    #[test]
270    fn subunit_display() {
271        let m = Money::from((1, 42)).display(Currency::SEK);
272        assert_eq!(m.subunit_display(), "142 öre");
273    }
274
275    #[test]
276    fn amount_display() {
277        let m = Money::from((1, 42)).display(Currency::SEK);
278        assert_eq!(m.amount(), "1.42");
279    }
280
281    #[test]
282    fn amount_display_small() {
283        let m = Money(7765).display(Currency::SEK);
284        assert_eq!(m.amount(), "0.08");
285    }
286
287    #[test]
288    fn amount_display_large() {
289        let m = Money::from((123456789, 99)).display(Currency::SEK);
290        assert_eq!(m.amount(), "123456789.99");
291    }
292
293    #[test]
294    fn subunit_amount_display() {
295        let m = Money::from((1, 42)).display(Currency::SEK);
296        assert_eq!(m.subunit_amount(), "142");
297    }
298
299    #[test]
300    fn add_money() {
301        assert_eq!(Money(1437), Money(100) + Money(1337));
302    }
303
304    #[test]
305    fn mul_money_f64() {
306        assert_eq!(Money(145) * 10f64, Money(1450));
307    }
308
309    #[test]
310    fn into_f64() {
311        let val: f64 = Money(145000).into();
312        assert_eq!(val, 1.45);
313    }
314
315    #[test]
316    fn sum_money_references() {
317        assert_eq!([Money(100), Money(1237)].iter().sum::<Money>(), Money(1337));
318    }
319
320    #[test]
321    fn sum_money_negative() {
322        assert_eq!(
323            [Money(-40000), Money(-3000), Money(-7500)]
324                .iter()
325                .sum::<Money>(),
326            Money(-50500)
327        );
328    }
329
330    #[test]
331    fn sum_money_mixed() {
332        assert_eq!(
333            [Money(-40000), Money(5000), Money(8000)]
334                .iter()
335                .sum::<Money>(),
336            Money(-27000)
337        );
338    }
339}