koprogo_api/application/use_cases/
unit_owner_use_cases.rs

1use crate::application::ports::{OwnerRepository, UnitOwnerRepository, UnitRepository};
2use crate::domain::entities::UnitOwner;
3use chrono::Utc;
4use std::sync::Arc;
5use uuid::Uuid;
6
7#[cfg(test)]
8#[path = "unit_owner_use_cases_test.rs"]
9mod unit_owner_use_cases_test;
10
11pub struct UnitOwnerUseCases {
12    unit_owner_repository: Arc<dyn UnitOwnerRepository>,
13    unit_repository: Arc<dyn UnitRepository>,
14    owner_repository: Arc<dyn OwnerRepository>,
15}
16
17impl UnitOwnerUseCases {
18    pub fn new(
19        unit_owner_repository: Arc<dyn UnitOwnerRepository>,
20        unit_repository: Arc<dyn UnitRepository>,
21        owner_repository: Arc<dyn OwnerRepository>,
22    ) -> Self {
23        Self {
24            unit_owner_repository,
25            unit_repository,
26            owner_repository,
27        }
28    }
29
30    /// Add an owner to a unit with specified ownership percentage
31    pub async fn add_owner_to_unit(
32        &self,
33        unit_id: Uuid,
34        owner_id: Uuid,
35        ownership_percentage: f64,
36        is_primary_contact: bool,
37    ) -> Result<UnitOwner, String> {
38        // Validate that unit exists
39        self.unit_repository
40            .find_by_id(unit_id)
41            .await?
42            .ok_or("Unit not found")?;
43
44        // Validate that owner exists
45        self.owner_repository
46            .find_by_id(owner_id)
47            .await?
48            .ok_or("Owner not found")?;
49
50        // Check if this owner is already active on this unit
51        if let Some(_existing) = self
52            .unit_owner_repository
53            .find_active_by_unit_and_owner(unit_id, owner_id)
54            .await?
55        {
56            return Err("Owner is already active on this unit".to_string());
57        }
58
59        // Validate total ownership percentage won't exceed 100% (Art. 577-2 §4 CC)
60        let current_total = self
61            .unit_owner_repository
62            .get_total_ownership_percentage(unit_id)
63            .await?;
64
65        // CRITICAL: This validation MUST block if total > 100.0% (Belgian legal requirement)
66        if current_total + ownership_percentage > 1.0 {
67            return Err(format!(
68                "Total ownership would exceed 100% (Art. 577-2 §4 CC). \
69                 Current: {:.2}%, adding: {:.2}%, total would be: {:.2}%",
70                current_total * 100.0,
71                ownership_percentage * 100.0,
72                (current_total + ownership_percentage) * 100.0
73            ));
74        }
75
76        // If this is primary contact, unset any existing primary contact
77        if is_primary_contact {
78            self.unset_all_primary_contacts(unit_id).await?;
79        }
80
81        // Create the unit-owner relationship
82        let unit_owner =
83            UnitOwner::new(unit_id, owner_id, ownership_percentage, is_primary_contact)?;
84
85        self.unit_owner_repository.create(&unit_owner).await
86    }
87
88    /// Remove an owner from a unit (sets end_date to now)
89    pub async fn remove_owner_from_unit(
90        &self,
91        unit_id: Uuid,
92        owner_id: Uuid,
93    ) -> Result<UnitOwner, String> {
94        // Find the active relationship
95        let mut unit_owner = self
96            .unit_owner_repository
97            .find_active_by_unit_and_owner(unit_id, owner_id)
98            .await?
99            .ok_or("Active unit-owner relationship not found")?;
100
101        // End the ownership
102        unit_owner.end_ownership(Utc::now())?;
103
104        self.unit_owner_repository.update(&unit_owner).await
105    }
106
107    /// Update the ownership percentage for a unit-owner relationship
108    pub async fn update_ownership_percentage(
109        &self,
110        unit_owner_id: Uuid,
111        new_percentage: f64,
112    ) -> Result<UnitOwner, String> {
113        // Find the unit-owner relationship
114        let mut unit_owner = self
115            .unit_owner_repository
116            .find_by_id(unit_owner_id)
117            .await?
118            .ok_or("Unit-owner relationship not found")?;
119
120        // Validate it's still active
121        if !unit_owner.is_active() {
122            return Err("Cannot update percentage of ended ownership".to_string());
123        }
124
125        // Calculate what the new total would be
126        let current_total = self
127            .unit_owner_repository
128            .get_total_ownership_percentage(unit_owner.unit_id)
129            .await?;
130        let old_percentage = unit_owner.ownership_percentage;
131        let new_total = current_total - old_percentage + new_percentage;
132
133        // CRITICAL: This validation MUST block if total > 100.0% (Art. 577-2 §4 CC)
134        if new_total > 1.0 {
135            return Err(format!(
136                "Total ownership would exceed 100% (Art. 577-2 §4 CC). \
137                 Current without this owner: {:.2}%, new percentage: {:.2}%, total would be: {:.2}%",
138                (current_total - old_percentage) * 100.0,
139                new_percentage * 100.0,
140                new_total * 100.0
141            ));
142        }
143
144        // Update the percentage
145        unit_owner.update_percentage(new_percentage)?;
146
147        self.unit_owner_repository.update(&unit_owner).await
148    }
149
150    /// Transfer ownership from one owner to another
151    pub async fn transfer_ownership(
152        &self,
153        from_owner_id: Uuid,
154        to_owner_id: Uuid,
155        unit_id: Uuid,
156    ) -> Result<(UnitOwner, UnitOwner), String> {
157        // Validate that both owners exist
158        self.owner_repository
159            .find_by_id(from_owner_id)
160            .await?
161            .ok_or("Source owner not found")?;
162
163        self.owner_repository
164            .find_by_id(to_owner_id)
165            .await?
166            .ok_or("Target owner not found")?;
167
168        // Get the active relationship from the source owner
169        let mut from_relationship = self
170            .unit_owner_repository
171            .find_active_by_unit_and_owner(unit_id, from_owner_id)
172            .await?
173            .ok_or("Source owner does not own this unit")?;
174
175        // Check if target owner already has an active relationship
176        if let Some(_existing) = self
177            .unit_owner_repository
178            .find_active_by_unit_and_owner(unit_id, to_owner_id)
179            .await?
180        {
181            return Err("Target owner already owns this unit".to_string());
182        }
183
184        // End the source ownership
185        let transfer_date = Utc::now();
186        from_relationship.end_ownership(transfer_date)?;
187
188        // Create new ownership for target owner with same percentage
189        let to_relationship = UnitOwner::new(
190            unit_id,
191            to_owner_id,
192            from_relationship.ownership_percentage,
193            from_relationship.is_primary_contact,
194        )?;
195
196        // Update both relationships
197        let ended_relationship = self
198            .unit_owner_repository
199            .update(&from_relationship)
200            .await?;
201        let new_relationship = self.unit_owner_repository.create(&to_relationship).await?;
202
203        Ok((ended_relationship, new_relationship))
204    }
205
206    /// Get all current owners of a unit
207    pub async fn get_unit_owners(&self, unit_id: Uuid) -> Result<Vec<UnitOwner>, String> {
208        // Validate unit exists
209        self.unit_repository
210            .find_by_id(unit_id)
211            .await?
212            .ok_or("Unit not found")?;
213
214        self.unit_owner_repository
215            .find_current_owners_by_unit(unit_id)
216            .await
217    }
218
219    /// Get all current units owned by an owner
220    pub async fn get_owner_units(&self, owner_id: Uuid) -> Result<Vec<UnitOwner>, String> {
221        // Validate owner exists
222        self.owner_repository
223            .find_by_id(owner_id)
224            .await?
225            .ok_or("Owner not found")?;
226
227        self.unit_owner_repository
228            .find_current_units_by_owner(owner_id)
229            .await
230    }
231
232    /// Get ownership history for a unit (including past owners)
233    pub async fn get_unit_ownership_history(
234        &self,
235        unit_id: Uuid,
236    ) -> Result<Vec<UnitOwner>, String> {
237        // Validate unit exists
238        self.unit_repository
239            .find_by_id(unit_id)
240            .await?
241            .ok_or("Unit not found")?;
242
243        self.unit_owner_repository
244            .find_all_owners_by_unit(unit_id)
245            .await
246    }
247
248    /// Get ownership history for an owner (including past units)
249    pub async fn get_owner_ownership_history(
250        &self,
251        owner_id: Uuid,
252    ) -> Result<Vec<UnitOwner>, String> {
253        // Validate owner exists
254        self.owner_repository
255            .find_by_id(owner_id)
256            .await?
257            .ok_or("Owner not found")?;
258
259        self.unit_owner_repository
260            .find_all_units_by_owner(owner_id)
261            .await
262    }
263
264    /// Set a unit-owner relationship as primary contact
265    pub async fn set_primary_contact(&self, unit_owner_id: Uuid) -> Result<UnitOwner, String> {
266        // Find the unit-owner relationship
267        let mut unit_owner = self
268            .unit_owner_repository
269            .find_by_id(unit_owner_id)
270            .await?
271            .ok_or("Unit-owner relationship not found")?;
272
273        // Validate it's still active
274        if !unit_owner.is_active() {
275            return Err("Cannot set primary contact for ended ownership".to_string());
276        }
277
278        // Unset all other primary contacts for this unit
279        self.unset_all_primary_contacts(unit_owner.unit_id).await?;
280
281        // Set this one as primary
282        unit_owner.set_primary_contact(true);
283
284        self.unit_owner_repository.update(&unit_owner).await
285    }
286
287    /// Get a specific unit-owner relationship by ID
288    pub async fn get_unit_owner(&self, id: Uuid) -> Result<Option<UnitOwner>, String> {
289        self.unit_owner_repository.find_by_id(id).await
290    }
291
292    /// Check if a unit has any active owners
293    pub async fn has_active_owners(&self, unit_id: Uuid) -> Result<bool, String> {
294        self.unit_owner_repository.has_active_owners(unit_id).await
295    }
296
297    /// Get the total ownership percentage for a unit
298    pub async fn get_total_ownership_percentage(&self, unit_id: Uuid) -> Result<f64, String> {
299        self.unit_owner_repository
300            .get_total_ownership_percentage(unit_id)
301            .await
302    }
303
304    // Helper method to unset all primary contacts for a unit
305    async fn unset_all_primary_contacts(&self, unit_id: Uuid) -> Result<(), String> {
306        let current_owners = self
307            .unit_owner_repository
308            .find_current_owners_by_unit(unit_id)
309            .await?;
310
311        for mut owner in current_owners {
312            if owner.is_primary_contact {
313                owner.set_primary_contact(false);
314                self.unit_owner_repository.update(&owner).await?;
315            }
316        }
317
318        Ok(())
319    }
320}