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 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 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 self.unit_repository
42 .find_by_id(unit_id)
43 .await?
44 .ok_or("Unit not found")?;
45
46 self.owner_repository
48 .find_by_id(owner_id)
49 .await?
50 .ok_or("Owner not found")?;
51
52 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 let current_total = self
63 .unit_owner_repository
64 .get_total_ownership_percentage(unit_id)
65 .await?;
66
67 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 is_primary_contact {
80 self.unset_all_primary_contacts(unit_id).await?;
81 }
82
83 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 pub async fn remove_owner_from_unit(
92 &self,
93 unit_id: Uuid,
94 owner_id: Uuid,
95 ) -> Result<UnitOwner, String> {
96 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 unit_owner.end_ownership(Utc::now())?;
105
106 self.unit_owner_repository.update(&unit_owner).await
107 }
108
109 pub async fn update_ownership_percentage(
111 &self,
112 unit_owner_id: Uuid,
113 new_percentage: Decimal,
114 ) -> Result<UnitOwner, String> {
115 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 if !unit_owner.is_active() {
124 return Err("Cannot update percentage of ended ownership".to_string());
125 }
126
127 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 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 unit_owner.update_percentage(new_percentage)?;
148
149 self.unit_owner_repository.update(&unit_owner).await
150 }
151
152 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 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 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 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 let transfer_date = Utc::now();
188 from_relationship.end_ownership(transfer_date)?;
189
190 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 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 pub async fn get_unit_owners(&self, unit_id: Uuid) -> Result<Vec<UnitOwner>, String> {
210 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 pub async fn get_owner_units(&self, owner_id: Uuid) -> Result<Vec<UnitOwner>, String> {
223 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 pub async fn get_unit_ownership_history(
236 &self,
237 unit_id: Uuid,
238 ) -> Result<Vec<UnitOwner>, String> {
239 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 pub async fn get_owner_ownership_history(
252 &self,
253 owner_id: Uuid,
254 ) -> Result<Vec<UnitOwner>, String> {
255 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 pub async fn set_primary_contact(&self, unit_owner_id: Uuid) -> Result<UnitOwner, String> {
268 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 if !unit_owner.is_active() {
277 return Err("Cannot set primary contact for ended ownership".to_string());
278 }
279
280 self.unset_all_primary_contacts(unit_owner.unit_id).await?;
282
283 unit_owner.set_primary_contact(true);
285
286 self.unit_owner_repository.update(&unit_owner).await
287 }
288
289 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 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 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 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}