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 fn to_response_dto(&self, owner: &Owner) -> OwnerResponseDto {
136 OwnerResponseDto {
137 id: owner.id.to_string(),
138 organization_id: owner.organization_id.to_string(),
139 user_id: owner.user_id.map(|id| id.to_string()),
140 first_name: owner.first_name.clone(),
141 last_name: owner.last_name.clone(),
142 email: owner.email.clone(),
143 phone: owner.phone.clone(),
144 address: owner.address.clone(),
145 city: owner.city.clone(),
146 postal_code: owner.postal_code.clone(),
147 country: owner.country.clone(),
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use async_trait::async_trait;
156 use std::collections::HashMap;
157 use std::sync::Mutex;
158
159 struct MockOwnerRepository {
160 items: Mutex<HashMap<Uuid, Owner>>,
161 }
162
163 impl MockOwnerRepository {
164 fn new() -> Self {
165 Self {
166 items: Mutex::new(HashMap::new()),
167 }
168 }
169 }
170
171 #[async_trait]
172 impl OwnerRepository for MockOwnerRepository {
173 async fn create(&self, owner: &Owner) -> Result<Owner, String> {
174 let mut items = self.items.lock().unwrap();
175 items.insert(owner.id, owner.clone());
176 Ok(owner.clone())
177 }
178
179 async fn find_by_id(&self, id: Uuid) -> Result<Option<Owner>, String> {
180 let items = self.items.lock().unwrap();
181 Ok(items.get(&id).cloned())
182 }
183
184 async fn find_by_user_id(&self, user_id: Uuid) -> Result<Option<Owner>, String> {
185 let items = self.items.lock().unwrap();
186 Ok(items.values().find(|o| o.user_id == Some(user_id)).cloned())
187 }
188
189 async fn find_by_user_id_and_organization(
190 &self,
191 user_id: Uuid,
192 organization_id: Uuid,
193 ) -> Result<Option<Owner>, String> {
194 let items = self.items.lock().unwrap();
195 Ok(items
196 .values()
197 .find(|o| o.user_id == Some(user_id) && o.organization_id == organization_id)
198 .cloned())
199 }
200
201 async fn find_by_email(&self, email: &str) -> Result<Option<Owner>, String> {
202 let items = self.items.lock().unwrap();
203 Ok(items.values().find(|o| o.email == email).cloned())
204 }
205
206 async fn find_all(&self) -> Result<Vec<Owner>, String> {
207 let items = self.items.lock().unwrap();
208 Ok(items.values().cloned().collect())
209 }
210
211 async fn find_all_paginated(
212 &self,
213 page_request: &PageRequest,
214 _filters: &OwnerFilters,
215 ) -> Result<(Vec<Owner>, i64), String> {
216 let items = self.items.lock().unwrap();
217 let all: Vec<Owner> = items.values().cloned().collect();
218 let total = all.len() as i64;
219 let offset = page_request.offset() as usize;
220 let limit = page_request.limit() as usize;
221 let page = all.into_iter().skip(offset).take(limit).collect();
222 Ok((page, total))
223 }
224
225 async fn update(&self, owner: &Owner) -> Result<Owner, String> {
226 let mut items = self.items.lock().unwrap();
227 items.insert(owner.id, owner.clone());
228 Ok(owner.clone())
229 }
230
231 async fn delete(&self, id: Uuid) -> Result<bool, String> {
232 let mut items = self.items.lock().unwrap();
233 Ok(items.remove(&id).is_some())
234 }
235 }
236
237 fn make_use_cases(repo: MockOwnerRepository) -> OwnerUseCases {
238 OwnerUseCases::new(Arc::new(repo))
239 }
240
241 fn make_create_dto(org_id: Uuid) -> CreateOwnerDto {
242 CreateOwnerDto {
243 organization_id: org_id.to_string(),
244 first_name: "Jean".to_string(),
245 last_name: "Dupont".to_string(),
246 email: "jean.dupont@example.com".to_string(),
247 phone: Some("+32470123456".to_string()),
248 address: "Rue de la Loi 16".to_string(),
249 city: "Bruxelles".to_string(),
250 postal_code: "1000".to_string(),
251 country: "Belgique".to_string(),
252 user_id: None,
253 }
254 }
255
256 #[tokio::test]
257 async fn test_create_owner_success() {
258 let repo = MockOwnerRepository::new();
259 let use_cases = make_use_cases(repo);
260 let org_id = Uuid::new_v4();
261
262 let result = use_cases.create_owner(make_create_dto(org_id)).await;
263
264 assert!(result.is_ok());
265 let dto = result.unwrap();
266 assert_eq!(dto.first_name, "Jean");
267 assert_eq!(dto.last_name, "Dupont");
268 assert_eq!(dto.email, "jean.dupont@example.com");
269 assert_eq!(dto.organization_id, org_id.to_string());
270 }
271
272 #[tokio::test]
273 async fn test_create_owner_duplicate_email() {
274 let repo = MockOwnerRepository::new();
275 let org_id = Uuid::new_v4();
276
277 let existing = Owner::new(
279 org_id,
280 "Marie".to_string(),
281 "Martin".to_string(),
282 "jean.dupont@example.com".to_string(),
283 None,
284 "Av. Louise 100".to_string(),
285 "Bruxelles".to_string(),
286 "1050".to_string(),
287 "Belgique".to_string(),
288 )
289 .unwrap();
290 repo.items.lock().unwrap().insert(existing.id, existing);
291
292 let use_cases = make_use_cases(repo);
293 let result = use_cases.create_owner(make_create_dto(org_id)).await;
294
295 assert!(result.is_err());
296 assert_eq!(result.unwrap_err(), "Email already exists");
297 }
298
299 #[tokio::test]
300 async fn test_get_owner_found() {
301 let repo = MockOwnerRepository::new();
302 let org_id = Uuid::new_v4();
303 let owner = Owner::new(
304 org_id,
305 "Jean".to_string(),
306 "Dupont".to_string(),
307 "jean@example.com".to_string(),
308 None,
309 "Rue Test 1".to_string(),
310 "Bruxelles".to_string(),
311 "1000".to_string(),
312 "Belgique".to_string(),
313 )
314 .unwrap();
315 let owner_id = owner.id;
316 repo.items.lock().unwrap().insert(owner.id, owner);
317
318 let use_cases = make_use_cases(repo);
319 let result = use_cases.get_owner(owner_id).await;
320
321 assert!(result.is_ok());
322 let dto = result.unwrap();
323 assert!(dto.is_some());
324 assert_eq!(dto.unwrap().first_name, "Jean");
325 }
326
327 #[tokio::test]
328 async fn test_get_owner_not_found() {
329 let repo = MockOwnerRepository::new();
330 let use_cases = make_use_cases(repo);
331
332 let result = use_cases.get_owner(Uuid::new_v4()).await;
333
334 assert!(result.is_ok());
335 assert!(result.unwrap().is_none());
336 }
337
338 #[tokio::test]
339 async fn test_list_owners() {
340 let repo = MockOwnerRepository::new();
341 let org_id = Uuid::new_v4();
342
343 let owner1 = Owner::new(
344 org_id,
345 "Jean".to_string(),
346 "Dupont".to_string(),
347 "jean@example.com".to_string(),
348 None,
349 "Rue A".to_string(),
350 "Bruxelles".to_string(),
351 "1000".to_string(),
352 "Belgique".to_string(),
353 )
354 .unwrap();
355 let owner2 = Owner::new(
356 org_id,
357 "Marie".to_string(),
358 "Martin".to_string(),
359 "marie@example.com".to_string(),
360 None,
361 "Rue B".to_string(),
362 "Liege".to_string(),
363 "4000".to_string(),
364 "Belgique".to_string(),
365 )
366 .unwrap();
367
368 {
369 let mut items = repo.items.lock().unwrap();
370 items.insert(owner1.id, owner1);
371 items.insert(owner2.id, owner2);
372 }
373
374 let use_cases = make_use_cases(repo);
375 let result = use_cases.list_owners().await;
376
377 assert!(result.is_ok());
378 assert_eq!(result.unwrap().len(), 2);
379 }
380
381 #[tokio::test]
382 async fn test_update_owner_success() {
383 let repo = MockOwnerRepository::new();
384 let org_id = Uuid::new_v4();
385 let owner = Owner::new(
386 org_id,
387 "Jean".to_string(),
388 "Dupont".to_string(),
389 "jean@example.com".to_string(),
390 None,
391 "Rue Test".to_string(),
392 "Bruxelles".to_string(),
393 "1000".to_string(),
394 "Belgique".to_string(),
395 )
396 .unwrap();
397 let owner_id = owner.id;
398 repo.items.lock().unwrap().insert(owner.id, owner);
399
400 let use_cases = make_use_cases(repo);
401 let result = use_cases
402 .update_owner(
403 owner_id,
404 "Pierre".to_string(),
405 "Durand".to_string(),
406 "pierre@example.com".to_string(),
407 Some("+32470999999".to_string()),
408 )
409 .await;
410
411 assert!(result.is_ok());
412 let dto = result.unwrap();
413 assert_eq!(dto.first_name, "Pierre");
414 assert_eq!(dto.last_name, "Durand");
415 assert_eq!(dto.email, "pierre@example.com");
416 assert_eq!(dto.phone, Some("+32470999999".to_string()));
417 }
418
419 #[tokio::test]
420 async fn test_update_owner_email_conflict() {
421 let repo = MockOwnerRepository::new();
422 let org_id = Uuid::new_v4();
423
424 let owner1 = Owner::new(
425 org_id,
426 "Jean".to_string(),
427 "Dupont".to_string(),
428 "jean@example.com".to_string(),
429 None,
430 "Rue A".to_string(),
431 "Bruxelles".to_string(),
432 "1000".to_string(),
433 "Belgique".to_string(),
434 )
435 .unwrap();
436 let owner1_id = owner1.id;
437
438 let owner2 = Owner::new(
439 org_id,
440 "Marie".to_string(),
441 "Martin".to_string(),
442 "marie@example.com".to_string(),
443 None,
444 "Rue B".to_string(),
445 "Liege".to_string(),
446 "4000".to_string(),
447 "Belgique".to_string(),
448 )
449 .unwrap();
450
451 {
452 let mut items = repo.items.lock().unwrap();
453 items.insert(owner1.id, owner1);
454 items.insert(owner2.id, owner2);
455 }
456
457 let use_cases = make_use_cases(repo);
458
459 let result = use_cases
461 .update_owner(
462 owner1_id,
463 "Jean".to_string(),
464 "Dupont".to_string(),
465 "marie@example.com".to_string(), None,
467 )
468 .await;
469
470 assert!(result.is_err());
471 assert_eq!(result.unwrap_err(), "Email already exists");
472 }
473}