1use crate::application::dto::{CreateOwnerDto, OwnerFilters, OwnerResponseDto, PageRequest};
2use crate::application::ports::OwnerRepository;
3use crate::domain::entities::Owner;
4use std::sync::Arc;
5use uuid::Uuid;
6
7pub struct OwnerUseCases {
8 repository: Arc<dyn OwnerRepository>,
9}
10
11impl OwnerUseCases {
12 pub fn new(repository: Arc<dyn OwnerRepository>) -> Self {
13 Self { repository }
14 }
15
16 pub async fn create_owner(&self, dto: CreateOwnerDto) -> Result<OwnerResponseDto, String> {
17 let organization_id = Uuid::parse_str(&dto.organization_id)
18 .map_err(|_| "Invalid organization_id format".to_string())?;
19
20 if (self.repository.find_by_email(&dto.email).await?).is_some() {
22 return Err("Email already exists".to_string());
23 }
24
25 let mut owner = Owner::new(
26 organization_id,
27 dto.first_name,
28 dto.last_name,
29 dto.email,
30 dto.phone,
31 dto.address,
32 dto.city,
33 dto.postal_code,
34 dto.country,
35 )?;
36
37 if let Some(user_id_str) = dto.user_id {
39 if !user_id_str.is_empty() {
40 owner.user_id = Some(
41 Uuid::parse_str(&user_id_str)
42 .map_err(|_| "Invalid user_id format".to_string())?,
43 );
44 }
45 }
46
47 let created = self.repository.create(&owner).await?;
48 Ok(self.to_response_dto(&created))
49 }
50
51 pub async fn get_owner(&self, id: Uuid) -> Result<Option<OwnerResponseDto>, String> {
52 let owner = self.repository.find_by_id(id).await?;
53 Ok(owner.map(|o| self.to_response_dto(&o)))
54 }
55
56 pub async fn find_owner_by_user_id(
57 &self,
58 user_id: Uuid,
59 ) -> Result<Option<OwnerResponseDto>, String> {
60 let owner = self.repository.find_by_user_id(user_id).await?;
61 Ok(owner.map(|o| self.to_response_dto(&o)))
62 }
63
64 pub async fn find_owner_by_user_id_and_organization(
65 &self,
66 user_id: Uuid,
67 organization_id: Uuid,
68 ) -> Result<Option<OwnerResponseDto>, String> {
69 let owner = self
70 .repository
71 .find_by_user_id_and_organization(user_id, organization_id)
72 .await?;
73 Ok(owner.map(|o| self.to_response_dto(&o)))
74 }
75
76 pub async fn list_owners(&self) -> Result<Vec<OwnerResponseDto>, String> {
77 let owners = self.repository.find_all().await?;
78 Ok(owners.iter().map(|o| self.to_response_dto(o)).collect())
79 }
80
81 pub async fn list_owners_paginated(
82 &self,
83 page_request: &PageRequest,
84 organization_id: Option<Uuid>,
85 ) -> Result<(Vec<OwnerResponseDto>, i64), String> {
86 let filters = OwnerFilters {
87 organization_id,
88 ..Default::default()
89 };
90
91 let (owners, total) = self
92 .repository
93 .find_all_paginated(page_request, &filters)
94 .await?;
95
96 let dtos = owners.iter().map(|o| self.to_response_dto(o)).collect();
97 Ok((dtos, total))
98 }
99
100 pub async fn update_owner(
101 &self,
102 id: Uuid,
103 first_name: String,
104 last_name: String,
105 email: String,
106 phone: Option<String>,
107 ) -> Result<OwnerResponseDto, String> {
108 let mut owner = self
110 .repository
111 .find_by_id(id)
112 .await?
113 .ok_or("Owner not found".to_string())?;
114
115 if owner.email != email {
117 if let Some(existing) = self.repository.find_by_email(&email).await? {
118 if existing.id != id {
119 return Err("Email already exists".to_string());
120 }
121 }
122 }
123
124 owner.first_name = first_name;
126 owner.last_name = last_name;
127 owner.email = email;
128 owner.phone = phone;
129
130 let updated = self.repository.update(&owner).await?;
132 Ok(self.to_response_dto(&updated))
133 }
134
135 pub async fn link_user_to_owner(
139 &self,
140 owner_id: Uuid,
141 user_id: Option<Uuid>,
142 ) -> Result<(), String> {
143 if let Some(uid) = user_id {
144 if let Some(existing) = self.repository.find_by_user_id(uid).await? {
146 if existing.id != owner_id {
147 return Err(format!(
148 "User is already linked to owner {} {} (ID: {})",
149 existing.first_name, existing.last_name, existing.id
150 ));
151 }
152 }
153 }
154
155 let updated = self.repository.set_user_link(owner_id, user_id).await?;
156 if !updated {
157 return Err("Owner not found".to_string());
158 }
159 Ok(())
160 }
161
162 fn to_response_dto(&self, owner: &Owner) -> OwnerResponseDto {
163 OwnerResponseDto {
164 id: owner.id.to_string(),
165 organization_id: owner.organization_id.to_string(),
166 user_id: owner.user_id.map(|id| id.to_string()),
167 first_name: owner.first_name.clone(),
168 last_name: owner.last_name.clone(),
169 email: owner.email.clone(),
170 phone: owner.phone.clone(),
171 address: owner.address.clone(),
172 city: owner.city.clone(),
173 postal_code: owner.postal_code.clone(),
174 country: owner.country.clone(),
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use async_trait::async_trait;
183 use std::collections::HashMap;
184 use std::sync::Mutex;
185
186 struct MockOwnerRepository {
187 items: Mutex<HashMap<Uuid, Owner>>,
188 }
189
190 impl MockOwnerRepository {
191 fn new() -> Self {
192 Self {
193 items: Mutex::new(HashMap::new()),
194 }
195 }
196 }
197
198 #[async_trait]
199 impl OwnerRepository for MockOwnerRepository {
200 async fn create(&self, owner: &Owner) -> Result<Owner, String> {
201 let mut items = self.items.lock().unwrap();
202 items.insert(owner.id, owner.clone());
203 Ok(owner.clone())
204 }
205
206 async fn find_by_id(&self, id: Uuid) -> Result<Option<Owner>, String> {
207 let items = self.items.lock().unwrap();
208 Ok(items.get(&id).cloned())
209 }
210
211 async fn find_by_user_id(&self, user_id: Uuid) -> Result<Option<Owner>, String> {
212 let items = self.items.lock().unwrap();
213 Ok(items.values().find(|o| o.user_id == Some(user_id)).cloned())
214 }
215
216 async fn find_by_user_id_and_organization(
217 &self,
218 user_id: Uuid,
219 organization_id: Uuid,
220 ) -> Result<Option<Owner>, String> {
221 let items = self.items.lock().unwrap();
222 Ok(items
223 .values()
224 .find(|o| o.user_id == Some(user_id) && o.organization_id == organization_id)
225 .cloned())
226 }
227
228 async fn find_by_email(&self, email: &str) -> Result<Option<Owner>, String> {
229 let items = self.items.lock().unwrap();
230 Ok(items.values().find(|o| o.email == email).cloned())
231 }
232
233 async fn find_all(&self) -> Result<Vec<Owner>, String> {
234 let items = self.items.lock().unwrap();
235 Ok(items.values().cloned().collect())
236 }
237
238 async fn find_all_paginated(
239 &self,
240 page_request: &PageRequest,
241 _filters: &OwnerFilters,
242 ) -> Result<(Vec<Owner>, i64), String> {
243 let items = self.items.lock().unwrap();
244 let all: Vec<Owner> = items.values().cloned().collect();
245 let total = all.len() as i64;
246 let offset = page_request.offset() as usize;
247 let limit = page_request.limit() as usize;
248 let page = all.into_iter().skip(offset).take(limit).collect();
249 Ok((page, total))
250 }
251
252 async fn update(&self, owner: &Owner) -> Result<Owner, String> {
253 let mut items = self.items.lock().unwrap();
254 items.insert(owner.id, owner.clone());
255 Ok(owner.clone())
256 }
257
258 async fn delete(&self, id: Uuid) -> Result<bool, String> {
259 let mut items = self.items.lock().unwrap();
260 Ok(items.remove(&id).is_some())
261 }
262
263 async fn set_user_link(
264 &self,
265 owner_id: Uuid,
266 user_id: Option<Uuid>,
267 ) -> Result<bool, String> {
268 let mut items = self.items.lock().unwrap();
269 if let Some(owner) = items.get_mut(&owner_id) {
270 owner.user_id = user_id;
271 Ok(true)
272 } else {
273 Ok(false)
274 }
275 }
276 }
277
278 fn make_use_cases(repo: MockOwnerRepository) -> OwnerUseCases {
279 OwnerUseCases::new(Arc::new(repo))
280 }
281
282 fn make_create_dto(org_id: Uuid) -> CreateOwnerDto {
283 CreateOwnerDto {
284 organization_id: org_id.to_string(),
285 first_name: "Jean".to_string(),
286 last_name: "Dupont".to_string(),
287 email: "jean.dupont@example.com".to_string(),
288 phone: Some("+32470123456".to_string()),
289 address: "Rue de la Loi 16".to_string(),
290 city: "Bruxelles".to_string(),
291 postal_code: "1000".to_string(),
292 country: "Belgique".to_string(),
293 user_id: None,
294 }
295 }
296
297 #[tokio::test]
298 async fn test_create_owner_success() {
299 let repo = MockOwnerRepository::new();
300 let use_cases = make_use_cases(repo);
301 let org_id = Uuid::new_v4();
302
303 let result = use_cases.create_owner(make_create_dto(org_id)).await;
304
305 assert!(result.is_ok());
306 let dto = result.unwrap();
307 assert_eq!(dto.first_name, "Jean");
308 assert_eq!(dto.last_name, "Dupont");
309 assert_eq!(dto.email, "jean.dupont@example.com");
310 assert_eq!(dto.organization_id, org_id.to_string());
311 }
312
313 #[tokio::test]
314 async fn test_create_owner_duplicate_email() {
315 let repo = MockOwnerRepository::new();
316 let org_id = Uuid::new_v4();
317
318 let existing = Owner::new(
320 org_id,
321 "Marie".to_string(),
322 "Martin".to_string(),
323 "jean.dupont@example.com".to_string(),
324 None,
325 "Av. Louise 100".to_string(),
326 "Bruxelles".to_string(),
327 "1050".to_string(),
328 "Belgique".to_string(),
329 )
330 .unwrap();
331 repo.items.lock().unwrap().insert(existing.id, existing);
332
333 let use_cases = make_use_cases(repo);
334 let result = use_cases.create_owner(make_create_dto(org_id)).await;
335
336 assert!(result.is_err());
337 assert_eq!(result.unwrap_err(), "Email already exists");
338 }
339
340 #[tokio::test]
341 async fn test_get_owner_found() {
342 let repo = MockOwnerRepository::new();
343 let org_id = Uuid::new_v4();
344 let owner = Owner::new(
345 org_id,
346 "Jean".to_string(),
347 "Dupont".to_string(),
348 "jean@example.com".to_string(),
349 None,
350 "Rue Test 1".to_string(),
351 "Bruxelles".to_string(),
352 "1000".to_string(),
353 "Belgique".to_string(),
354 )
355 .unwrap();
356 let owner_id = owner.id;
357 repo.items.lock().unwrap().insert(owner.id, owner);
358
359 let use_cases = make_use_cases(repo);
360 let result = use_cases.get_owner(owner_id).await;
361
362 assert!(result.is_ok());
363 let dto = result.unwrap();
364 assert!(dto.is_some());
365 assert_eq!(dto.unwrap().first_name, "Jean");
366 }
367
368 #[tokio::test]
369 async fn test_get_owner_not_found() {
370 let repo = MockOwnerRepository::new();
371 let use_cases = make_use_cases(repo);
372
373 let result = use_cases.get_owner(Uuid::new_v4()).await;
374
375 assert!(result.is_ok());
376 assert!(result.unwrap().is_none());
377 }
378
379 #[tokio::test]
380 async fn test_list_owners() {
381 let repo = MockOwnerRepository::new();
382 let org_id = Uuid::new_v4();
383
384 let owner1 = Owner::new(
385 org_id,
386 "Jean".to_string(),
387 "Dupont".to_string(),
388 "jean@example.com".to_string(),
389 None,
390 "Rue A".to_string(),
391 "Bruxelles".to_string(),
392 "1000".to_string(),
393 "Belgique".to_string(),
394 )
395 .unwrap();
396 let owner2 = Owner::new(
397 org_id,
398 "Marie".to_string(),
399 "Martin".to_string(),
400 "marie@example.com".to_string(),
401 None,
402 "Rue B".to_string(),
403 "Liege".to_string(),
404 "4000".to_string(),
405 "Belgique".to_string(),
406 )
407 .unwrap();
408
409 {
410 let mut items = repo.items.lock().unwrap();
411 items.insert(owner1.id, owner1);
412 items.insert(owner2.id, owner2);
413 }
414
415 let use_cases = make_use_cases(repo);
416 let result = use_cases.list_owners().await;
417
418 assert!(result.is_ok());
419 assert_eq!(result.unwrap().len(), 2);
420 }
421
422 #[tokio::test]
423 async fn test_update_owner_success() {
424 let repo = MockOwnerRepository::new();
425 let org_id = Uuid::new_v4();
426 let owner = Owner::new(
427 org_id,
428 "Jean".to_string(),
429 "Dupont".to_string(),
430 "jean@example.com".to_string(),
431 None,
432 "Rue Test".to_string(),
433 "Bruxelles".to_string(),
434 "1000".to_string(),
435 "Belgique".to_string(),
436 )
437 .unwrap();
438 let owner_id = owner.id;
439 repo.items.lock().unwrap().insert(owner.id, owner);
440
441 let use_cases = make_use_cases(repo);
442 let result = use_cases
443 .update_owner(
444 owner_id,
445 "Pierre".to_string(),
446 "Durand".to_string(),
447 "pierre@example.com".to_string(),
448 Some("+32470999999".to_string()),
449 )
450 .await;
451
452 assert!(result.is_ok());
453 let dto = result.unwrap();
454 assert_eq!(dto.first_name, "Pierre");
455 assert_eq!(dto.last_name, "Durand");
456 assert_eq!(dto.email, "pierre@example.com");
457 assert_eq!(dto.phone, Some("+32470999999".to_string()));
458 }
459
460 #[tokio::test]
461 async fn test_update_owner_email_conflict() {
462 let repo = MockOwnerRepository::new();
463 let org_id = Uuid::new_v4();
464
465 let owner1 = Owner::new(
466 org_id,
467 "Jean".to_string(),
468 "Dupont".to_string(),
469 "jean@example.com".to_string(),
470 None,
471 "Rue A".to_string(),
472 "Bruxelles".to_string(),
473 "1000".to_string(),
474 "Belgique".to_string(),
475 )
476 .unwrap();
477 let owner1_id = owner1.id;
478
479 let owner2 = Owner::new(
480 org_id,
481 "Marie".to_string(),
482 "Martin".to_string(),
483 "marie@example.com".to_string(),
484 None,
485 "Rue B".to_string(),
486 "Liege".to_string(),
487 "4000".to_string(),
488 "Belgique".to_string(),
489 )
490 .unwrap();
491
492 {
493 let mut items = repo.items.lock().unwrap();
494 items.insert(owner1.id, owner1);
495 items.insert(owner2.id, owner2);
496 }
497
498 let use_cases = make_use_cases(repo);
499
500 let result = use_cases
502 .update_owner(
503 owner1_id,
504 "Jean".to_string(),
505 "Dupont".to_string(),
506 "marie@example.com".to_string(), None,
508 )
509 .await;
510
511 assert!(result.is_err());
512 assert_eq!(result.unwrap_err(), "Email already exists");
513 }
514}