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 org_id,
58 building_id,
59 ExpenseCategory::Maintenance,
60 "Test".to_string(),
61 dec!(1000),
62 Utc::now(),
63 None,
64 None,
65 None, )
67 .unwrap();
68
69 let unit = Unit::new(
70 org_id,
71 building_id,
72 "A101".to_string(),
73 UnitType::Apartment,
74 Some(1),
75 75.0,
76 dec!(50), )
78 .unwrap();
79
80 let share = ExpenseCalculator::calculate_unit_share(&expense, &unit);
81 assert_eq!(share, dec!(50)); }
83
84 #[test]
85 fn test_calculate_total_expenses() {
86 let org_id = Uuid::new_v4();
87 let building_id = Uuid::new_v4();
88
89 let expenses = vec![
90 Expense::new(
91 org_id,
92 building_id,
93 ExpenseCategory::Maintenance,
94 "Test 1".to_string(),
95 dec!(100),
96 Utc::now(),
97 None,
98 None,
99 None, )
101 .unwrap(),
102 Expense::new(
103 org_id,
104 building_id,
105 ExpenseCategory::Repairs,
106 "Test 2".to_string(),
107 dec!(200),
108 Utc::now(),
109 None,
110 None,
111 None, )
113 .unwrap(),
114 ];
115
116 let total = ExpenseCalculator::calculate_total_expenses(&expenses);
117 assert_eq!(total, dec!(300));
118 }
119
120 #[test]
121 fn test_calculate_paid_and_unpaid() {
122 let org_id = Uuid::new_v4();
123 let building_id = Uuid::new_v4();
124 let syndic_id = Uuid::new_v4();
125
126 let mut expense1 = Expense::new(
127 org_id,
128 building_id,
129 ExpenseCategory::Maintenance,
130 "Test 1".to_string(),
131 dec!(100),
132 Utc::now(),
133 None,
134 None,
135 None, )
137 .unwrap();
138 expense1.submit_for_approval().unwrap();
140 expense1.approve(syndic_id).unwrap();
141 let _ = expense1.mark_as_paid();
142
143 let expense2 = Expense::new(
144 org_id,
145 building_id,
146 ExpenseCategory::Repairs,
147 "Test 2".to_string(),
148 dec!(200),
149 Utc::now(),
150 None,
151 None,
152 None, )
154 .unwrap();
155
156 let expenses = vec![expense1, expense2];
157
158 let paid = ExpenseCalculator::calculate_paid_expenses(&expenses);
159 let unpaid = ExpenseCalculator::calculate_unpaid_expenses(&expenses);
160
161 assert_eq!(paid, dec!(100));
162 assert_eq!(unpaid, dec!(200));
163 }
164}