koprogo_api/domain/services/
expense_calculator.rs1use crate::domain::entities::{Expense, Unit};
6use rust_decimal::Decimal;
7use rust_decimal_macros::dec;
8
9const QUOTA_DIVISOR: Decimal = dec!(1000);
10
11pub struct ExpenseCalculator;
13
14impl ExpenseCalculator {
15 pub fn calculate_unit_share(expense: &Expense, unit: &Unit) -> Decimal {
17 expense.amount * (unit.quota / QUOTA_DIVISOR)
18 }
19
20 pub fn calculate_total_expenses(expenses: &[Expense]) -> Decimal {
22 expenses.iter().map(|e| e.amount).sum()
23 }
24
25 pub fn calculate_paid_expenses(expenses: &[Expense]) -> Decimal {
27 expenses
28 .iter()
29 .filter(|e| e.is_paid())
30 .map(|e| e.amount)
31 .sum()
32 }
33
34 pub fn calculate_unpaid_expenses(expenses: &[Expense]) -> Decimal {
36 expenses
37 .iter()
38 .filter(|e| !e.is_paid())
39 .map(|e| e.amount)
40 .sum()
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47 use crate::domain::entities::{ExpenseCategory, UnitType};
48 use chrono::Utc;
49 use uuid::Uuid;
50
51 #[test]
52 fn test_calculate_unit_share() {
53 let org_id = Uuid::new_v4();
54 let building_id = Uuid::new_v4();
55
56 let expense = Expense::new(
57 Uuid::new_v4(), org_id,
59 building_id,
60 ExpenseCategory::Maintenance,
61 "Test".to_string(),
62 dec!(1000),
63 Utc::now(),
64 None,
65 None,
66 None, )
68 .unwrap();
69
70 let unit = Unit::new(
71 org_id,
72 building_id,
73 "A101".to_string(),
74 UnitType::Apartment,
75 Some(1),
76 75.0,
77 dec!(50), )
79 .unwrap();
80
81 let share = ExpenseCalculator::calculate_unit_share(&expense, &unit);
82 assert_eq!(share, dec!(50)); }
84
85 #[test]
86 fn test_calculate_total_expenses() {
87 let org_id = Uuid::new_v4();
88 let building_id = Uuid::new_v4();
89
90 let expenses = vec![
91 Expense::new(
92 Uuid::new_v4(), org_id,
94 building_id,
95 ExpenseCategory::Maintenance,
96 "Test 1".to_string(),
97 dec!(100),
98 Utc::now(),
99 None,
100 None,
101 None, )
103 .unwrap(),
104 Expense::new(
105 Uuid::new_v4(), org_id,
107 building_id,
108 ExpenseCategory::Repairs,
109 "Test 2".to_string(),
110 dec!(200),
111 Utc::now(),
112 None,
113 None,
114 None, )
116 .unwrap(),
117 ];
118
119 let total = ExpenseCalculator::calculate_total_expenses(&expenses);
120 assert_eq!(total, dec!(300));
121 }
122
123 #[test]
124 fn test_calculate_paid_and_unpaid() {
125 let org_id = Uuid::new_v4();
126 let building_id = Uuid::new_v4();
127 let syndic_id = Uuid::new_v4();
128
129 let mut expense1 = Expense::new(
130 Uuid::new_v4(), org_id,
132 building_id,
133 ExpenseCategory::Maintenance,
134 "Test 1".to_string(),
135 dec!(100),
136 Utc::now(),
137 None,
138 None,
139 None, )
141 .unwrap();
142 expense1.submit_for_approval().unwrap();
144 expense1.approve(syndic_id).unwrap();
145 let _ = expense1.mark_as_paid();
146
147 let expense2 = Expense::new(
148 Uuid::new_v4(), org_id,
150 building_id,
151 ExpenseCategory::Repairs,
152 "Test 2".to_string(),
153 dec!(200),
154 Utc::now(),
155 None,
156 None,
157 None, )
159 .unwrap();
160
161 let expenses = vec![expense1, expense2];
162
163 let paid = ExpenseCalculator::calculate_paid_expenses(&expenses);
164 let unpaid = ExpenseCalculator::calculate_unpaid_expenses(&expenses);
165
166 assert_eq!(paid, dec!(100));
167 assert_eq!(unpaid, dec!(200));
168 }
169}