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