koprogo_api/application/use_cases/
etat_date_use_cases.rs

1use crate::application::dto::{
2    CreateEtatDateRequest, EtatDateResponse, EtatDateStatsResponse, PageRequest,
3    UpdateEtatDateAdditionalDataRequest, UpdateEtatDateFinancialRequest,
4};
5use crate::application::ports::{
6    BuildingRepository, EtatDateRepository, UnitOwnerRepository, UnitRepository,
7};
8use crate::domain::entities::{EtatDate, EtatDateStatus};
9use f64;
10use std::sync::Arc;
11use uuid::Uuid;
12
13pub struct EtatDateUseCases {
14    repository: Arc<dyn EtatDateRepository>,
15    unit_repository: Arc<dyn UnitRepository>,
16    building_repository: Arc<dyn BuildingRepository>,
17    unit_owner_repository: Arc<dyn UnitOwnerRepository>,
18}
19
20impl EtatDateUseCases {
21    pub fn new(
22        repository: Arc<dyn EtatDateRepository>,
23        unit_repository: Arc<dyn UnitRepository>,
24        building_repository: Arc<dyn BuildingRepository>,
25        unit_owner_repository: Arc<dyn UnitOwnerRepository>,
26    ) -> Self {
27        Self {
28            repository,
29            unit_repository,
30            building_repository,
31            unit_owner_repository,
32        }
33    }
34
35    /// Create a new état daté request
36    pub async fn create_etat_date(
37        &self,
38        request: CreateEtatDateRequest,
39    ) -> Result<EtatDateResponse, String> {
40        // Verify unit exists
41        let unit = self
42            .unit_repository
43            .find_by_id(request.unit_id)
44            .await?
45            .ok_or_else(|| "Unit not found".to_string())?;
46
47        // Verify building exists
48        let building = self
49            .building_repository
50            .find_by_id(request.building_id)
51            .await?
52            .ok_or_else(|| "Building not found".to_string())?;
53
54        // Get unit ownership info (quotes-parts)
55        let unit_owners = self
56            .unit_owner_repository
57            .find_current_owners_by_unit(request.unit_id)
58            .await?;
59
60        if unit_owners.is_empty() {
61            return Err("Unit has no active owners".to_string());
62        }
63
64        // Calculate total quote-parts (should be 100% or close)
65        let total_quota: f64 = unit_owners.iter().map(|uo| uo.ownership_percentage).sum();
66
67        // For simplicity, use total quota as both ordinary and extraordinary
68        // In a real system, these might be stored separately per unit
69        let ordinary_charges_quota = total_quota * 100.0; // Convert to ownership_percentage
70        let extraordinary_charges_quota = ordinary_charges_quota;
71
72        // Create état daté
73        let etat_date = EtatDate::new(
74            request.organization_id,
75            request.building_id,
76            request.unit_id,
77            request.reference_date,
78            request.language,
79            request.notary_name,
80            request.notary_email,
81            request.notary_phone,
82            building.name.clone(),
83            building.address.clone(),
84            unit.unit_number.clone(),
85            unit.floor.map(|f| f.to_string()),
86            Some(unit.surface_area),
87            ordinary_charges_quota,
88            extraordinary_charges_quota,
89        )?;
90
91        let created = self.repository.create(&etat_date).await?;
92        Ok(EtatDateResponse::from(created))
93    }
94
95    /// Get état daté by ID
96    pub async fn get_etat_date(&self, id: Uuid) -> Result<Option<EtatDateResponse>, String> {
97        let etat_date = self.repository.find_by_id(id).await?;
98        Ok(etat_date.map(EtatDateResponse::from))
99    }
100
101    /// Get état daté by reference number
102    pub async fn get_by_reference_number(
103        &self,
104        reference_number: &str,
105    ) -> Result<Option<EtatDateResponse>, String> {
106        let etat_date = self
107            .repository
108            .find_by_reference_number(reference_number)
109            .await?;
110        Ok(etat_date.map(EtatDateResponse::from))
111    }
112
113    /// List états datés for a unit
114    pub async fn list_by_unit(&self, unit_id: Uuid) -> Result<Vec<EtatDateResponse>, String> {
115        let etats = self.repository.find_by_unit(unit_id).await?;
116        Ok(etats.into_iter().map(EtatDateResponse::from).collect())
117    }
118
119    /// List états datés for a building
120    pub async fn list_by_building(
121        &self,
122        building_id: Uuid,
123    ) -> Result<Vec<EtatDateResponse>, String> {
124        let etats = self.repository.find_by_building(building_id).await?;
125        Ok(etats.into_iter().map(EtatDateResponse::from).collect())
126    }
127
128    /// List états datés paginated
129    pub async fn list_paginated(
130        &self,
131        page_request: &PageRequest,
132        organization_id: Option<Uuid>,
133        status: Option<EtatDateStatus>,
134    ) -> Result<(Vec<EtatDateResponse>, i64), String> {
135        let (etats, total) = self
136            .repository
137            .find_all_paginated(page_request, organization_id, status)
138            .await?;
139
140        let dtos = etats.into_iter().map(EtatDateResponse::from).collect();
141        Ok((dtos, total))
142    }
143
144    /// Mark état daté as in progress
145    pub async fn mark_in_progress(&self, id: Uuid) -> Result<EtatDateResponse, String> {
146        let mut etat_date = self
147            .repository
148            .find_by_id(id)
149            .await?
150            .ok_or_else(|| "État daté not found".to_string())?;
151
152        etat_date.mark_in_progress()?;
153
154        let updated = self.repository.update(&etat_date).await?;
155        Ok(EtatDateResponse::from(updated))
156    }
157
158    /// Mark état daté as generated (with PDF file path)
159    pub async fn mark_generated(
160        &self,
161        id: Uuid,
162        pdf_file_path: String,
163    ) -> Result<EtatDateResponse, String> {
164        let mut etat_date = self
165            .repository
166            .find_by_id(id)
167            .await?
168            .ok_or_else(|| "État daté not found".to_string())?;
169
170        etat_date.mark_generated(pdf_file_path)?;
171
172        let updated = self.repository.update(&etat_date).await?;
173        Ok(EtatDateResponse::from(updated))
174    }
175
176    /// Mark état daté as delivered to notary
177    pub async fn mark_delivered(&self, id: Uuid) -> Result<EtatDateResponse, String> {
178        let mut etat_date = self
179            .repository
180            .find_by_id(id)
181            .await?
182            .ok_or_else(|| "État daté not found".to_string())?;
183
184        etat_date.mark_delivered()?;
185
186        let updated = self.repository.update(&etat_date).await?;
187        Ok(EtatDateResponse::from(updated))
188    }
189
190    /// Update financial data
191    pub async fn update_financial_data(
192        &self,
193        id: Uuid,
194        request: UpdateEtatDateFinancialRequest,
195    ) -> Result<EtatDateResponse, String> {
196        let mut etat_date = self
197            .repository
198            .find_by_id(id)
199            .await?
200            .ok_or_else(|| "État daté not found".to_string())?;
201
202        etat_date.update_financial_data(
203            request.owner_balance,
204            request.arrears_amount,
205            request.monthly_provision_amount,
206            request.total_balance,
207            request.approved_works_unpaid,
208        )?;
209
210        let updated = self.repository.update(&etat_date).await?;
211        Ok(EtatDateResponse::from(updated))
212    }
213
214    /// Update additional data (sections 7-16)
215    pub async fn update_additional_data(
216        &self,
217        id: Uuid,
218        request: UpdateEtatDateAdditionalDataRequest,
219    ) -> Result<EtatDateResponse, String> {
220        let mut etat_date = self
221            .repository
222            .find_by_id(id)
223            .await?
224            .ok_or_else(|| "État daté not found".to_string())?;
225
226        etat_date.update_additional_data(request.additional_data)?;
227
228        let updated = self.repository.update(&etat_date).await?;
229        Ok(EtatDateResponse::from(updated))
230    }
231
232    /// Get overdue états datés (>10 days, not generated yet)
233    pub async fn list_overdue(
234        &self,
235        organization_id: Uuid,
236    ) -> Result<Vec<EtatDateResponse>, String> {
237        let etats = self.repository.find_overdue(organization_id).await?;
238        Ok(etats.into_iter().map(EtatDateResponse::from).collect())
239    }
240
241    /// Get expired états datés (>3 months from reference date)
242    pub async fn list_expired(
243        &self,
244        organization_id: Uuid,
245    ) -> Result<Vec<EtatDateResponse>, String> {
246        let etats = self.repository.find_expired(organization_id).await?;
247        Ok(etats.into_iter().map(EtatDateResponse::from).collect())
248    }
249
250    /// Delete état daté
251    pub async fn delete_etat_date(&self, id: Uuid) -> Result<bool, String> {
252        self.repository.delete(id).await
253    }
254
255    /// Get statistics for dashboard
256    pub async fn get_stats(&self, organization_id: Uuid) -> Result<EtatDateStatsResponse, String> {
257        self.repository.get_stats(organization_id).await
258    }
259}