koprogo_api/domain/entities/
unit_owner.rs1use chrono::{DateTime, Utc};
2use uuid::Uuid;
3
4#[derive(Debug, Clone)]
11pub struct UnitOwner {
12 pub id: Uuid,
13 pub unit_id: Uuid,
14 pub owner_id: Uuid,
15
16 pub ownership_percentage: f64,
19
20 pub start_date: DateTime<Utc>,
22
23 pub end_date: Option<DateTime<Utc>>,
25
26 pub is_primary_contact: bool,
28
29 pub created_at: DateTime<Utc>,
30 pub updated_at: DateTime<Utc>,
31}
32
33impl UnitOwner {
34 pub fn new(
36 unit_id: Uuid,
37 owner_id: Uuid,
38 ownership_percentage: f64,
39 is_primary_contact: bool,
40 ) -> Result<Self, String> {
41 if ownership_percentage <= 0.0 || ownership_percentage > 1.0 {
43 return Err("Ownership percentage must be between 0 and 1".to_string());
44 }
45
46 Ok(Self {
47 id: Uuid::new_v4(),
48 unit_id,
49 owner_id,
50 ownership_percentage,
51 start_date: Utc::now(),
52 end_date: None,
53 is_primary_contact,
54 created_at: Utc::now(),
55 updated_at: Utc::now(),
56 })
57 }
58
59 pub fn new_with_start_date(
61 unit_id: Uuid,
62 owner_id: Uuid,
63 ownership_percentage: f64,
64 is_primary_contact: bool,
65 start_date: DateTime<Utc>,
66 ) -> Result<Self, String> {
67 if ownership_percentage <= 0.0 || ownership_percentage > 1.0 {
68 return Err("Ownership percentage must be between 0 and 1".to_string());
69 }
70
71 Ok(Self {
72 id: Uuid::new_v4(),
73 unit_id,
74 owner_id,
75 ownership_percentage,
76 start_date,
77 end_date: None,
78 is_primary_contact,
79 created_at: Utc::now(),
80 updated_at: Utc::now(),
81 })
82 }
83
84 pub fn is_active(&self) -> bool {
86 self.end_date.is_none()
87 }
88
89 pub fn end_ownership(&mut self, end_date: DateTime<Utc>) -> Result<(), String> {
91 if end_date <= self.start_date {
92 return Err("End date must be after start date".to_string());
93 }
94
95 self.end_date = Some(end_date);
96 self.updated_at = Utc::now();
97 Ok(())
98 }
99
100 pub fn update_percentage(&mut self, new_percentage: f64) -> Result<(), String> {
102 if new_percentage <= 0.0 || new_percentage > 1.0 {
103 return Err("Ownership percentage must be between 0 and 1".to_string());
104 }
105
106 self.ownership_percentage = new_percentage;
107 self.updated_at = Utc::now();
108 Ok(())
109 }
110
111 pub fn set_primary_contact(&mut self, is_primary: bool) {
113 self.is_primary_contact = is_primary;
114 self.updated_at = Utc::now();
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_create_unit_owner() {
124 let unit_id = Uuid::new_v4();
125 let owner_id = Uuid::new_v4();
126
127 let unit_owner = UnitOwner::new(unit_id, owner_id, 0.5, true).unwrap();
128
129 assert_eq!(unit_owner.unit_id, unit_id);
130 assert_eq!(unit_owner.owner_id, owner_id);
131 assert_eq!(unit_owner.ownership_percentage, 0.5);
132 assert!(unit_owner.is_primary_contact);
133 assert!(unit_owner.is_active());
134 }
135
136 #[test]
137 fn test_invalid_ownership_percentage() {
138 let unit_id = Uuid::new_v4();
139 let owner_id = Uuid::new_v4();
140
141 let result = UnitOwner::new(unit_id, owner_id, 1.5, false);
143 assert!(result.is_err());
144
145 let result = UnitOwner::new(unit_id, owner_id, 0.0, false);
147 assert!(result.is_err());
148
149 let result = UnitOwner::new(unit_id, owner_id, -0.5, false);
150 assert!(result.is_err());
151 }
152
153 #[test]
154 fn test_end_ownership() {
155 let unit_id = Uuid::new_v4();
156 let owner_id = Uuid::new_v4();
157
158 let mut unit_owner = UnitOwner::new(unit_id, owner_id, 1.0, true).unwrap();
159
160 assert!(unit_owner.is_active());
161
162 let end_date = Utc::now() + chrono::Duration::days(1);
163 unit_owner.end_ownership(end_date).unwrap();
164
165 assert!(!unit_owner.is_active());
166 assert_eq!(unit_owner.end_date, Some(end_date));
167 }
168
169 #[test]
170 fn test_invalid_end_date() {
171 let unit_id = Uuid::new_v4();
172 let owner_id = Uuid::new_v4();
173
174 let mut unit_owner = UnitOwner::new(unit_id, owner_id, 1.0, true).unwrap();
175
176 let invalid_end_date = unit_owner.start_date - chrono::Duration::days(1);
178 let result = unit_owner.end_ownership(invalid_end_date);
179
180 assert!(result.is_err());
181 }
182
183 #[test]
184 fn test_update_percentage() {
185 let unit_id = Uuid::new_v4();
186 let owner_id = Uuid::new_v4();
187
188 let mut unit_owner = UnitOwner::new(unit_id, owner_id, 0.5, true).unwrap();
189
190 unit_owner.update_percentage(0.75).unwrap();
191 assert_eq!(unit_owner.ownership_percentage, 0.75);
192
193 let result = unit_owner.update_percentage(1.5);
195 assert!(result.is_err());
196 }
197
198 #[test]
199 fn test_update_percentage_boundary_values() {
200 let unit_id = Uuid::new_v4();
201 let owner_id = Uuid::new_v4();
202
203 let mut unit_owner = UnitOwner::new(unit_id, owner_id, 0.5, false).unwrap();
204
205 assert!(unit_owner.update_percentage(1.0).is_ok());
207 assert_eq!(unit_owner.ownership_percentage, 1.0);
208
209 assert!(unit_owner.update_percentage(0.0).is_err());
211
212 assert!(unit_owner.update_percentage(0.0001).is_ok());
214 assert_eq!(unit_owner.ownership_percentage, 0.0001);
215
216 assert!(unit_owner.update_percentage(1.0001).is_err());
218
219 assert!(unit_owner.update_percentage(-0.5).is_err());
221 }
222
223 #[test]
224 fn test_set_primary_contact() {
225 let unit_id = Uuid::new_v4();
226 let owner_id = Uuid::new_v4();
227
228 let mut unit_owner = UnitOwner::new(unit_id, owner_id, 0.5, false).unwrap();
229
230 assert!(!unit_owner.is_primary_contact);
231
232 unit_owner.set_primary_contact(true);
233 assert!(unit_owner.is_primary_contact);
234
235 unit_owner.set_primary_contact(false);
236 assert!(!unit_owner.is_primary_contact);
237 }
238
239 #[test]
240 fn test_ownership_percentage_precision() {
241 let unit_id = Uuid::new_v4();
242 let owner_id = Uuid::new_v4();
243
244 let unit_owner = UnitOwner::new(unit_id, owner_id, 0.3333, false).unwrap();
246 assert_eq!(unit_owner.ownership_percentage, 0.3333);
247
248 let unit_owner = UnitOwner::new(unit_id, owner_id, 0.0001, false).unwrap();
250 assert_eq!(unit_owner.ownership_percentage, 0.0001);
251 }
252
253 #[test]
254 fn test_end_ownership_updates_end_date() {
255 let unit_id = Uuid::new_v4();
256 let owner_id = Uuid::new_v4();
257
258 let mut unit_owner = UnitOwner::new(unit_id, owner_id, 1.0, true).unwrap();
259
260 assert!(unit_owner.end_date.is_none());
261
262 let end_date = Utc::now() + chrono::Duration::days(30);
263 unit_owner.end_ownership(end_date).unwrap();
264
265 assert!(unit_owner.end_date.is_some());
266 assert_eq!(unit_owner.end_date.unwrap(), end_date);
267 }
268
269 #[test]
270 fn test_cannot_end_ownership_twice() {
271 let unit_id = Uuid::new_v4();
272 let owner_id = Uuid::new_v4();
273
274 let mut unit_owner = UnitOwner::new(unit_id, owner_id, 1.0, true).unwrap();
275
276 let first_end = Utc::now() + chrono::Duration::days(1);
277 unit_owner.end_ownership(first_end).unwrap();
278
279 let second_end = Utc::now() + chrono::Duration::days(2);
281 let result = unit_owner.end_ownership(second_end);
282 assert!(result.is_ok());
283 assert_eq!(unit_owner.end_date.unwrap(), second_end);
284 }
285
286 #[test]
287 fn test_timestamps_are_set() {
288 let unit_id = Uuid::new_v4();
289 let owner_id = Uuid::new_v4();
290
291 let before = Utc::now();
292 let unit_owner = UnitOwner::new(unit_id, owner_id, 0.5, false).unwrap();
293 let after = Utc::now();
294
295 assert!(unit_owner.created_at >= before);
297 assert!(unit_owner.created_at <= after);
298
299 let diff = (unit_owner.created_at - unit_owner.updated_at)
301 .num_milliseconds()
302 .abs();
303 assert!(diff < 1);
304 }
305
306 #[test]
307 fn test_updated_at_changes_on_modification() {
308 let unit_id = Uuid::new_v4();
309 let owner_id = Uuid::new_v4();
310
311 let mut unit_owner = UnitOwner::new(unit_id, owner_id, 0.5, false).unwrap();
312 let original_updated_at = unit_owner.updated_at;
313
314 std::thread::sleep(std::time::Duration::from_millis(10));
316
317 unit_owner.update_percentage(0.6).unwrap();
318 assert!(unit_owner.updated_at > original_updated_at);
319
320 let previous_updated = unit_owner.updated_at;
321 std::thread::sleep(std::time::Duration::from_millis(10));
322
323 unit_owner.set_primary_contact(true);
324 assert!(unit_owner.updated_at > previous_updated);
325 }
326
327 #[test]
328 fn test_100_percent_ownership_is_valid() {
329 let unit_id = Uuid::new_v4();
330 let owner_id = Uuid::new_v4();
331
332 let unit_owner = UnitOwner::new(unit_id, owner_id, 1.0, true).unwrap();
333 assert_eq!(unit_owner.ownership_percentage, 1.0);
334 }
335
336 #[test]
337 fn test_multiple_owners_scenario_percentages() {
338 let unit_id = Uuid::new_v4();
339 let owner1_id = Uuid::new_v4();
340 let owner2_id = Uuid::new_v4();
341 let owner3_id = Uuid::new_v4();
342
343 let owner1 = UnitOwner::new(unit_id, owner1_id, 0.5, true).unwrap();
345 let owner2 = UnitOwner::new(unit_id, owner2_id, 0.3, false).unwrap();
346 let owner3 = UnitOwner::new(unit_id, owner3_id, 0.2, false).unwrap();
347
348 assert_eq!(owner1.ownership_percentage, 0.5);
349 assert_eq!(owner2.ownership_percentage, 0.3);
350 assert_eq!(owner3.ownership_percentage, 0.2);
351
352 let total =
354 owner1.ownership_percentage + owner2.ownership_percentage + owner3.ownership_percentage;
355 assert!((total - 1.0).abs() < f64::EPSILON);
356 }
357}