koprogo_api/domain/services/
expense_calculator.rs

1use crate::domain::entities::{Expense, Unit};
2
3/// Service de domaine pour calculer la répartition des charges
4pub struct ExpenseCalculator;
5
6impl ExpenseCalculator {
7    /// Calcule le montant dû par un lot selon sa quote-part
8    pub fn calculate_unit_share(expense: &Expense, unit: &Unit) -> f64 {
9        expense.amount * (unit.quota / 1000.0)
10    }
11
12    /// Calcule le total des charges pour un ensemble de dépenses
13    pub fn calculate_total_expenses(expenses: &[Expense]) -> f64 {
14        expenses.iter().map(|e| e.amount).sum()
15    }
16
17    /// Calcule le montant total payé
18    pub fn calculate_paid_expenses(expenses: &[Expense]) -> f64 {
19        expenses
20            .iter()
21            .filter(|e| e.is_paid())
22            .map(|e| e.amount)
23            .sum()
24    }
25
26    /// Calcule le montant total impayé
27    pub fn calculate_unpaid_expenses(expenses: &[Expense]) -> f64 {
28        expenses
29            .iter()
30            .filter(|e| !e.is_paid())
31            .map(|e| e.amount)
32            .sum()
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use crate::domain::entities::{ExpenseCategory, UnitType};
40    use chrono::Utc;
41    use uuid::Uuid;
42
43    #[test]
44    fn test_calculate_unit_share() {
45        let org_id = Uuid::new_v4();
46        let building_id = Uuid::new_v4();
47
48        let expense = Expense::new(
49            org_id,
50            building_id,
51            ExpenseCategory::Maintenance,
52            "Test".to_string(),
53            1000.0,
54            Utc::now(),
55            None,
56            None,
57        )
58        .unwrap();
59
60        let unit = Unit::new(
61            org_id,
62            building_id,
63            "A101".to_string(),
64            UnitType::Apartment,
65            Some(1),
66            75.0,
67            50.0, // 50/1000 = 5%
68        )
69        .unwrap();
70
71        let share = ExpenseCalculator::calculate_unit_share(&expense, &unit);
72        assert_eq!(share, 50.0); // 5% de 1000€ = 50€
73    }
74
75    #[test]
76    fn test_calculate_total_expenses() {
77        let org_id = Uuid::new_v4();
78        let building_id = Uuid::new_v4();
79
80        let expenses = vec![
81            Expense::new(
82                org_id,
83                building_id,
84                ExpenseCategory::Maintenance,
85                "Test 1".to_string(),
86                100.0,
87                Utc::now(),
88                None,
89                None,
90            )
91            .unwrap(),
92            Expense::new(
93                org_id,
94                building_id,
95                ExpenseCategory::Repairs,
96                "Test 2".to_string(),
97                200.0,
98                Utc::now(),
99                None,
100                None,
101            )
102            .unwrap(),
103        ];
104
105        let total = ExpenseCalculator::calculate_total_expenses(&expenses);
106        assert_eq!(total, 300.0);
107    }
108
109    #[test]
110    fn test_calculate_paid_and_unpaid() {
111        let org_id = Uuid::new_v4();
112        let building_id = Uuid::new_v4();
113
114        let mut expense1 = Expense::new(
115            org_id,
116            building_id,
117            ExpenseCategory::Maintenance,
118            "Test 1".to_string(),
119            100.0,
120            Utc::now(),
121            None,
122            None,
123        )
124        .unwrap();
125        expense1.mark_as_paid();
126
127        let expense2 = Expense::new(
128            org_id,
129            building_id,
130            ExpenseCategory::Repairs,
131            "Test 2".to_string(),
132            200.0,
133            Utc::now(),
134            None,
135            None,
136        )
137        .unwrap();
138
139        let expenses = vec![expense1, expense2];
140
141        let paid = ExpenseCalculator::calculate_paid_expenses(&expenses);
142        let unpaid = ExpenseCalculator::calculate_unpaid_expenses(&expenses);
143
144        assert_eq!(paid, 100.0);
145        assert_eq!(unpaid, 200.0);
146    }
147}