Skip to main content

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