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