koprogo_api/application/use_cases/
charge_distribution_use_cases.rs

1use crate::application::dto::ChargeDistributionResponseDto;
2use crate::application::ports::{
3    ChargeDistributionRepository, ExpenseRepository, UnitOwnerRepository,
4};
5use crate::domain::entities::{ApprovalStatus, ChargeDistribution};
6use std::sync::Arc;
7use uuid::Uuid;
8
9pub struct ChargeDistributionUseCases {
10    distribution_repository: Arc<dyn ChargeDistributionRepository>,
11    expense_repository: Arc<dyn ExpenseRepository>,
12    unit_owner_repository: Arc<dyn UnitOwnerRepository>,
13}
14
15impl ChargeDistributionUseCases {
16    pub fn new(
17        distribution_repository: Arc<dyn ChargeDistributionRepository>,
18        expense_repository: Arc<dyn ExpenseRepository>,
19        unit_owner_repository: Arc<dyn UnitOwnerRepository>,
20    ) -> Self {
21        Self {
22            distribution_repository,
23            expense_repository,
24            unit_owner_repository,
25        }
26    }
27
28    /// Calculer et sauvegarder la répartition des charges pour une facture approuvée
29    pub async fn calculate_and_save_distribution(
30        &self,
31        expense_id: Uuid,
32    ) -> Result<Vec<ChargeDistributionResponseDto>, String> {
33        // 1. Récupérer la facture
34        let expense = self
35            .expense_repository
36            .find_by_id(expense_id)
37            .await?
38            .ok_or_else(|| "Expense/Invoice not found".to_string())?;
39
40        // 2. Vérifier que la facture est approuvée
41        if expense.approval_status != ApprovalStatus::Approved {
42            return Err(format!(
43                "Cannot calculate distribution for non-approved invoice (status: {:?})",
44                expense.approval_status
45            ));
46        }
47
48        // 3. Récupérer le montant TTC à répartir
49        let total_amount = expense.amount_incl_vat.unwrap_or(expense.amount);
50
51        // 4. Récupérer toutes les relations unit-owner actives pour ce bâtiment
52        let unit_ownerships = self
53            .unit_owner_repository
54            .find_active_by_building(expense.building_id)
55            .await?;
56
57        if unit_ownerships.is_empty() {
58            return Err("No active unit-owner relationships found for this building".to_string());
59        }
60
61        // 5. Calculer les distributions
62        let distributions =
63            ChargeDistribution::calculate_distributions(expense_id, total_amount, unit_ownerships)?;
64
65        // 6. Sauvegarder en masse
66        let saved_distributions = self
67            .distribution_repository
68            .create_bulk(&distributions)
69            .await?;
70
71        // 7. Convertir en DTOs
72        Ok(saved_distributions
73            .iter()
74            .map(|d| self.to_response_dto(d))
75            .collect())
76    }
77
78    /// Récupérer la répartition d'une facture
79    pub async fn get_distribution_by_expense(
80        &self,
81        expense_id: Uuid,
82    ) -> Result<Vec<ChargeDistributionResponseDto>, String> {
83        let distributions = self
84            .distribution_repository
85            .find_by_expense(expense_id)
86            .await?;
87        Ok(distributions
88            .iter()
89            .map(|d| self.to_response_dto(d))
90            .collect())
91    }
92
93    /// Récupérer toutes les distributions pour un propriétaire
94    pub async fn get_distributions_by_owner(
95        &self,
96        owner_id: Uuid,
97    ) -> Result<Vec<ChargeDistributionResponseDto>, String> {
98        let distributions = self.distribution_repository.find_by_owner(owner_id).await?;
99        Ok(distributions
100            .iter()
101            .map(|d| self.to_response_dto(d))
102            .collect())
103    }
104
105    /// Récupérer le montant total dû par un propriétaire
106    pub async fn get_total_due_by_owner(&self, owner_id: Uuid) -> Result<f64, String> {
107        self.distribution_repository
108            .get_total_due_by_owner(owner_id)
109            .await
110    }
111
112    /// Supprimer les distributions d'une facture (si annulée)
113    pub async fn delete_distribution_by_expense(&self, expense_id: Uuid) -> Result<(), String> {
114        self.distribution_repository
115            .delete_by_expense(expense_id)
116            .await
117    }
118
119    fn to_response_dto(&self, distribution: &ChargeDistribution) -> ChargeDistributionResponseDto {
120        ChargeDistributionResponseDto {
121            id: distribution.id.to_string(),
122            expense_id: distribution.expense_id.to_string(),
123            unit_id: distribution.unit_id.to_string(),
124            owner_id: distribution.owner_id.to_string(),
125            quota_percentage: distribution.quota_percentage,
126            amount_due: distribution.amount_due,
127            created_at: distribution.created_at.to_rfc3339(),
128        }
129    }
130}