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%
60        let current_total = self
61            .unit_owner_repository
62            .get_total_ownership_percentage(unit_id)
63            .await?;
64
65        if current_total + ownership_percentage > 1.0 {
66            return Err(format!(
67                "Total ownership would exceed 100% (current: {:.2}%, adding: {:.2}%)",
68                current_total * 100.0,
69                ownership_percentage * 100.0
70            ));
71        }
72
73        // If this is primary contact, unset any existing primary contact
74        if is_primary_contact {
75            self.unset_all_primary_contacts(unit_id).await?;
76        }
77
78        // Create the unit-owner relationship
79        let unit_owner =
80            UnitOwner::new(unit_id, owner_id, ownership_percentage, is_primary_contact)?;
81
82        self.unit_owner_repository.create(&unit_owner).await
83    }
84
85    /// Remove an owner from a unit (sets end_date to now)
86    pub async fn remove_owner_from_unit(
87        &self,
88        unit_id: Uuid,
89        owner_id: Uuid,
90    ) -> Result<UnitOwner, String> {
91        // Find the active relationship
92        let mut unit_owner = self
93            .unit_owner_repository
94            .find_active_by_unit_and_owner(unit_id, owner_id)
95            .await?
96            .ok_or("Active unit-owner relationship not found")?;
97
98        // End the ownership
99        unit_owner.end_ownership(Utc::now())?;
100
101        self.unit_owner_repository.update(&unit_owner).await
102    }
103
104    /// Update the ownership percentage for a unit-owner relationship
105    pub async fn update_ownership_percentage(
106        &self,
107        unit_owner_id: Uuid,
108        new_percentage: f64,
109    ) -> Result<UnitOwner, String> {
110        // Find the unit-owner relationship
111        let mut unit_owner = self
112            .unit_owner_repository
113            .find_by_id(unit_owner_id)
114            .await?
115            .ok_or("Unit-owner relationship not found")?;
116
117        // Validate it's still active
118        if !unit_owner.is_active() {
119            return Err("Cannot update percentage of ended ownership".to_string());
120        }
121
122        // Calculate what the new total would be
123        let current_total = self
124            .unit_owner_repository
125            .get_total_ownership_percentage(unit_owner.unit_id)
126            .await?;
127        let old_percentage = unit_owner.ownership_percentage;
128        let new_total = current_total - old_percentage + new_percentage;
129
130        if new_total > 1.0 {
131            return Err(format!(
132                "Total ownership would exceed 100% (current: {:.2}%, new total: {:.2}%)",
133                current_total * 100.0,
134                new_total * 100.0
135            ));
136        }
137
138        // Update the percentage
139        unit_owner.update_percentage(new_percentage)?;
140
141        self.unit_owner_repository.update(&unit_owner).await
142    }
143
144    /// Transfer ownership from one owner to another
145    pub async fn transfer_ownership(
146        &self,
147        from_owner_id: Uuid,
148        to_owner_id: Uuid,
149        unit_id: Uuid,
150    ) -> Result<(UnitOwner, UnitOwner), String> {
151        // Validate that both owners exist
152        self.owner_repository
153            .find_by_id(from_owner_id)
154            .await?
155            .ok_or("Source owner not found")?;
156
157        self.owner_repository
158            .find_by_id(to_owner_id)
159            .await?
160            .ok_or("Target owner not found")?;
161
162        // Get the active relationship from the source owner
163        let mut from_relationship = self
164            .unit_owner_repository
165            .find_active_by_unit_and_owner(unit_id, from_owner_id)
166            .await?
167            .ok_or("Source owner does not own this unit")?;
168
169        // Check if target owner already has an active relationship
170        if let Some(_existing) = self
171            .unit_owner_repository
172            .find_active_by_unit_and_owner(unit_id, to_owner_id)
173            .await?
174        {
175            return Err("Target owner already owns this unit".to_string());
176        }
177
178        // End the source ownership
179        let transfer_date = Utc::now();
180        from_relationship.end_ownership(transfer_date)?;
181
182        // Create new ownership for target owner with same percentage
183        let to_relationship = UnitOwner::new(
184            unit_id,
185            to_owner_id,
186            from_relationship.ownership_percentage,
187            from_relationship.is_primary_contact,
188        )?;
189
190        // Update both relationships
191        let ended_relationship = self
192            .unit_owner_repository
193            .update(&from_relationship)
194            .await?;
195        let new_relationship = self.unit_owner_repository.create(&to_relationship).await?;
196
197        Ok((ended_relationship, new_relationship))
198    }
199
200    /// Get all current owners of a unit
201    pub async fn get_unit_owners(&self, unit_id: Uuid) -> Result<Vec<UnitOwner>, String> {
202        // Validate unit exists
203        self.unit_repository
204            .find_by_id(unit_id)
205            .await?
206            .ok_or("Unit not found")?;
207
208        self.unit_owner_repository
209            .find_current_owners_by_unit(unit_id)
210            .await
211    }
212
213    /// Get all current units owned by an owner
214    pub async fn get_owner_units(&self, owner_id: Uuid) -> Result<Vec<UnitOwner>, String> {
215        // Validate owner exists
216        self.owner_repository
217            .find_by_id(owner_id)
218            .await?
219            .ok_or("Owner not found")?;
220
221        self.unit_owner_repository
222            .find_current_units_by_owner(owner_id)
223            .await
224    }
225
226    /// Get ownership history for a unit (including past owners)
227    pub async fn get_unit_ownership_history(
228        &self,
229        unit_id: Uuid,
230    ) -> Result<Vec<UnitOwner>, String> {
231        // Validate unit exists
232        self.unit_repository
233            .find_by_id(unit_id)
234            .await?
235            .ok_or("Unit not found")?;
236
237        self.unit_owner_repository
238            .find_all_owners_by_unit(unit_id)
239            .await
240    }
241
242    /// Get ownership history for an owner (including past units)
243    pub async fn get_owner_ownership_history(
244        &self,
245        owner_id: Uuid,
246    ) -> Result<Vec<UnitOwner>, String> {
247        // Validate owner exists
248        self.owner_repository
249            .find_by_id(owner_id)
250            .await?
251            .ok_or("Owner not found")?;
252
253        self.unit_owner_repository
254            .find_all_units_by_owner(owner_id)
255            .await
256    }
257
258    /// Set a unit-owner relationship as primary contact
259    pub async fn set_primary_contact(&self, unit_owner_id: Uuid) -> Result<UnitOwner, String> {
260        // Find the unit-owner relationship
261        let mut unit_owner = self
262            .unit_owner_repository
263            .find_by_id(unit_owner_id)
264            .await?
265            .ok_or("Unit-owner relationship not found")?;
266
267        // Validate it's still active
268        if !unit_owner.is_active() {
269            return Err("Cannot set primary contact for ended ownership".to_string());
270        }
271
272        // Unset all other primary contacts for this unit
273        self.unset_all_primary_contacts(unit_owner.unit_id).await?;
274
275        // Set this one as primary
276        unit_owner.set_primary_contact(true);
277
278        self.unit_owner_repository.update(&unit_owner).await
279    }
280
281    /// Get a specific unit-owner relationship by ID
282    pub async fn get_unit_owner(&self, id: Uuid) -> Result<Option<UnitOwner>, String> {
283        self.unit_owner_repository.find_by_id(id).await
284    }
285
286    /// Check if a unit has any active owners
287    pub async fn has_active_owners(&self, unit_id: Uuid) -> Result<bool, String> {
288        self.unit_owner_repository.has_active_owners(unit_id).await
289    }
290
291    /// Get the total ownership percentage for a unit
292    pub async fn get_total_ownership_percentage(&self, unit_id: Uuid) -> Result<f64, String> {
293        self.unit_owner_repository
294            .get_total_ownership_percentage(unit_id)
295            .await
296    }
297
298    // Helper method to unset all primary contacts for a unit
299    async fn unset_all_primary_contacts(&self, unit_id: Uuid) -> Result<(), String> {
300        let current_owners = self
301            .unit_owner_repository
302            .find_current_owners_by_unit(unit_id)
303            .await?;
304
305        for mut owner in current_owners {
306            if owner.is_primary_contact {
307                owner.set_primary_contact(false);
308                self.unit_owner_repository.update(&owner).await?;
309            }
310        }
311
312        Ok(())
313    }
314}