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