koprogo_api/application/use_cases/
unit_owner_use_cases.rs1use 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 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 self.unit_repository
40 .find_by_id(unit_id)
41 .await?
42 .ok_or("Unit not found")?;
43
44 self.owner_repository
46 .find_by_id(owner_id)
47 .await?
48 .ok_or("Owner not found")?;
49
50 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 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 {
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 is_primary_contact {
78 self.unset_all_primary_contacts(unit_id).await?;
79 }
80
81 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 pub async fn remove_owner_from_unit(
90 &self,
91 unit_id: Uuid,
92 owner_id: Uuid,
93 ) -> Result<UnitOwner, String> {
94 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 unit_owner.end_ownership(Utc::now())?;
103
104 self.unit_owner_repository.update(&unit_owner).await
105 }
106
107 pub async fn update_ownership_percentage(
109 &self,
110 unit_owner_id: Uuid,
111 new_percentage: f64,
112 ) -> Result<UnitOwner, String> {
113 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 if !unit_owner.is_active() {
122 return Err("Cannot update percentage of ended ownership".to_string());
123 }
124
125 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 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 unit_owner.update_percentage(new_percentage)?;
146
147 self.unit_owner_repository.update(&unit_owner).await
148 }
149
150 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 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 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 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 let transfer_date = Utc::now();
186 from_relationship.end_ownership(transfer_date)?;
187
188 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 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 pub async fn get_unit_owners(&self, unit_id: Uuid) -> Result<Vec<UnitOwner>, String> {
208 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 pub async fn get_owner_units(&self, owner_id: Uuid) -> Result<Vec<UnitOwner>, String> {
221 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 pub async fn get_unit_ownership_history(
234 &self,
235 unit_id: Uuid,
236 ) -> Result<Vec<UnitOwner>, String> {
237 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 pub async fn get_owner_ownership_history(
250 &self,
251 owner_id: Uuid,
252 ) -> Result<Vec<UnitOwner>, String> {
253 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 pub async fn set_primary_contact(&self, unit_owner_id: Uuid) -> Result<UnitOwner, String> {
266 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 if !unit_owner.is_active() {
275 return Err("Cannot set primary contact for ended ownership".to_string());
276 }
277
278 self.unset_all_primary_contacts(unit_owner.unit_id).await?;
280
281 unit_owner.set_primary_contact(true);
283
284 self.unit_owner_repository.update(&unit_owner).await
285 }
286
287 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 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 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 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}