1use crate::application::ports::{ResolutionRepository, VoteRepository};
2use crate::domain::entities::{
3 MajorityType, Resolution, ResolutionStatus, ResolutionType, Vote, VoteChoice,
4};
5use std::sync::Arc;
6use uuid::Uuid;
7
8pub struct ResolutionUseCases {
9 resolution_repository: Arc<dyn ResolutionRepository>,
10 vote_repository: Arc<dyn VoteRepository>,
11}
12
13impl ResolutionUseCases {
14 pub fn new(
15 resolution_repository: Arc<dyn ResolutionRepository>,
16 vote_repository: Arc<dyn VoteRepository>,
17 ) -> Self {
18 Self {
19 resolution_repository,
20 vote_repository,
21 }
22 }
23
24 pub async fn create_resolution(
26 &self,
27 meeting_id: Uuid,
28 title: String,
29 description: String,
30 resolution_type: ResolutionType,
31 majority_required: MajorityType,
32 ) -> Result<Resolution, String> {
33 let resolution = Resolution::new(
34 meeting_id,
35 title,
36 description,
37 resolution_type,
38 majority_required,
39 )?;
40
41 self.resolution_repository.create(&resolution).await
42 }
43
44 pub async fn get_resolution(&self, id: Uuid) -> Result<Option<Resolution>, String> {
46 self.resolution_repository.find_by_id(id).await
47 }
48
49 pub async fn get_meeting_resolutions(
51 &self,
52 meeting_id: Uuid,
53 ) -> Result<Vec<Resolution>, String> {
54 self.resolution_repository
55 .find_by_meeting_id(meeting_id)
56 .await
57 }
58
59 pub async fn get_resolutions_by_status(
61 &self,
62 status: ResolutionStatus,
63 ) -> Result<Vec<Resolution>, String> {
64 self.resolution_repository.find_by_status(status).await
65 }
66
67 pub async fn update_resolution(&self, resolution: &Resolution) -> Result<Resolution, String> {
69 if resolution.status != ResolutionStatus::Pending {
70 return Err("Cannot update a resolution that is not pending".to_string());
71 }
72
73 self.resolution_repository.update(resolution).await
74 }
75
76 pub async fn delete_resolution(&self, id: Uuid) -> Result<bool, String> {
78 let votes = self.vote_repository.find_by_resolution_id(id).await?;
80 if !votes.is_empty() {
81 return Err("Cannot delete a resolution with existing votes".to_string());
82 }
83
84 self.resolution_repository.delete(id).await
85 }
86
87 pub async fn cast_vote(
89 &self,
90 resolution_id: Uuid,
91 owner_id: Uuid,
92 unit_id: Uuid,
93 vote_choice: VoteChoice,
94 voting_power: f64,
95 proxy_owner_id: Option<Uuid>,
96 ) -> Result<Vote, String> {
97 let resolution = self
99 .resolution_repository
100 .find_by_id(resolution_id)
101 .await?
102 .ok_or_else(|| "Resolution not found".to_string())?;
103
104 if resolution.status != ResolutionStatus::Pending {
105 return Err("Cannot vote on a resolution that is not pending".to_string());
106 }
107
108 if self
110 .vote_repository
111 .has_voted(resolution_id, unit_id)
112 .await?
113 {
114 return Err("This unit has already voted on this resolution".to_string());
115 }
116
117 let vote = Vote::new(
119 resolution_id,
120 owner_id,
121 unit_id,
122 vote_choice.clone(),
123 voting_power,
124 proxy_owner_id,
125 )?;
126
127 let created_vote = self.vote_repository.create(&vote).await?;
128
129 self.update_resolution_vote_counts(resolution_id).await?;
131
132 Ok(created_vote)
133 }
134
135 pub async fn change_vote(&self, vote_id: Uuid, new_choice: VoteChoice) -> Result<Vote, String> {
137 let mut vote = self
138 .vote_repository
139 .find_by_id(vote_id)
140 .await?
141 .ok_or_else(|| "Vote not found".to_string())?;
142
143 let resolution = self
145 .resolution_repository
146 .find_by_id(vote.resolution_id)
147 .await?
148 .ok_or_else(|| "Resolution not found".to_string())?;
149
150 if resolution.status != ResolutionStatus::Pending {
151 return Err("Cannot change vote on a closed resolution".to_string());
152 }
153
154 vote.change_vote(new_choice)?;
156 let updated_vote = self.vote_repository.update(&vote).await?;
157
158 self.update_resolution_vote_counts(vote.resolution_id)
160 .await?;
161
162 Ok(updated_vote)
163 }
164
165 pub async fn get_resolution_votes(&self, resolution_id: Uuid) -> Result<Vec<Vote>, String> {
167 self.vote_repository
168 .find_by_resolution_id(resolution_id)
169 .await
170 }
171
172 pub async fn get_owner_votes(&self, owner_id: Uuid) -> Result<Vec<Vote>, String> {
174 self.vote_repository.find_by_owner_id(owner_id).await
175 }
176
177 pub async fn close_voting(
179 &self,
180 resolution_id: Uuid,
181 total_voting_power: f64,
182 ) -> Result<Resolution, String> {
183 let mut resolution = self
184 .resolution_repository
185 .find_by_id(resolution_id)
186 .await?
187 .ok_or_else(|| "Resolution not found".to_string())?;
188
189 if resolution.status != ResolutionStatus::Pending {
190 return Err("Resolution voting is already closed".to_string());
191 }
192
193 resolution.close_voting(total_voting_power)?;
195
196 self.resolution_repository
198 .close_voting(resolution_id, resolution.status.clone())
199 .await?;
200
201 self.resolution_repository
203 .find_by_id(resolution_id)
204 .await?
205 .ok_or_else(|| "Resolution not found after closing".to_string())
206 }
207
208 pub async fn get_meeting_vote_summary(
210 &self,
211 meeting_id: Uuid,
212 ) -> Result<Vec<Resolution>, String> {
213 self.resolution_repository
214 .get_meeting_vote_summary(meeting_id)
215 .await
216 }
217
218 async fn update_resolution_vote_counts(&self, resolution_id: Uuid) -> Result<(), String> {
220 let (pour_count, contre_count, abstention_count) = self
222 .vote_repository
223 .count_by_resolution_and_choice(resolution_id)
224 .await?;
225
226 let (pour_power, contre_power, abstention_power) = self
228 .vote_repository
229 .sum_voting_power_by_resolution(resolution_id)
230 .await?;
231
232 self.resolution_repository
234 .update_vote_counts(
235 resolution_id,
236 pour_count,
237 contre_count,
238 abstention_count,
239 pour_power,
240 contre_power,
241 abstention_power,
242 )
243 .await
244 }
245
246 pub async fn has_unit_voted(&self, resolution_id: Uuid, unit_id: Uuid) -> Result<bool, String> {
248 self.vote_repository.has_voted(resolution_id, unit_id).await
249 }
250
251 pub async fn get_vote_statistics(&self, resolution_id: Uuid) -> Result<VoteStatistics, String> {
253 let resolution = self
254 .resolution_repository
255 .find_by_id(resolution_id)
256 .await?
257 .ok_or_else(|| "Resolution not found".to_string())?;
258
259 Ok(VoteStatistics {
260 total_votes: resolution.total_votes(),
261 vote_count_pour: resolution.vote_count_pour,
262 vote_count_contre: resolution.vote_count_contre,
263 vote_count_abstention: resolution.vote_count_abstention,
264 total_voting_power_pour: resolution.total_voting_power_pour,
265 total_voting_power_contre: resolution.total_voting_power_contre,
266 total_voting_power_abstention: resolution.total_voting_power_abstention,
267 pour_percentage: resolution.pour_percentage(),
268 contre_percentage: resolution.contre_percentage(),
269 abstention_percentage: resolution.abstention_percentage(),
270 status: resolution.status,
271 })
272 }
273}
274
275#[derive(Debug, Clone)]
277pub struct VoteStatistics {
278 pub total_votes: i32,
279 pub vote_count_pour: i32,
280 pub vote_count_contre: i32,
281 pub vote_count_abstention: i32,
282 pub total_voting_power_pour: f64,
283 pub total_voting_power_contre: f64,
284 pub total_voting_power_abstention: f64,
285 pub pour_percentage: f64,
286 pub contre_percentage: f64,
287 pub abstention_percentage: f64,
288 pub status: ResolutionStatus,
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use crate::application::ports::ResolutionRepository;
295 use crate::application::ports::VoteRepository;
296 use async_trait::async_trait;
297 use std::collections::HashMap;
298 use std::sync::Mutex;
299
300 struct MockResolutionRepository {
302 resolutions: Mutex<HashMap<Uuid, Resolution>>,
303 }
304
305 impl MockResolutionRepository {
306 fn new() -> Self {
307 Self {
308 resolutions: Mutex::new(HashMap::new()),
309 }
310 }
311 }
312
313 #[async_trait]
314 impl ResolutionRepository for MockResolutionRepository {
315 async fn create(&self, resolution: &Resolution) -> Result<Resolution, String> {
316 self.resolutions
317 .lock()
318 .unwrap()
319 .insert(resolution.id, resolution.clone());
320 Ok(resolution.clone())
321 }
322
323 async fn find_by_id(&self, id: Uuid) -> Result<Option<Resolution>, String> {
324 Ok(self.resolutions.lock().unwrap().get(&id).cloned())
325 }
326
327 async fn find_by_meeting_id(&self, meeting_id: Uuid) -> Result<Vec<Resolution>, String> {
328 Ok(self
329 .resolutions
330 .lock()
331 .unwrap()
332 .values()
333 .filter(|r| r.meeting_id == meeting_id)
334 .cloned()
335 .collect())
336 }
337
338 async fn find_by_status(
339 &self,
340 status: ResolutionStatus,
341 ) -> Result<Vec<Resolution>, String> {
342 Ok(self
343 .resolutions
344 .lock()
345 .unwrap()
346 .values()
347 .filter(|r| r.status == status)
348 .cloned()
349 .collect())
350 }
351
352 async fn update(&self, resolution: &Resolution) -> Result<Resolution, String> {
353 self.resolutions
354 .lock()
355 .unwrap()
356 .insert(resolution.id, resolution.clone());
357 Ok(resolution.clone())
358 }
359
360 async fn delete(&self, id: Uuid) -> Result<bool, String> {
361 Ok(self.resolutions.lock().unwrap().remove(&id).is_some())
362 }
363
364 async fn update_vote_counts(
365 &self,
366 resolution_id: Uuid,
367 vote_count_pour: i32,
368 vote_count_contre: i32,
369 vote_count_abstention: i32,
370 total_voting_power_pour: f64,
371 total_voting_power_contre: f64,
372 total_voting_power_abstention: f64,
373 ) -> Result<(), String> {
374 if let Some(resolution) = self.resolutions.lock().unwrap().get_mut(&resolution_id) {
375 resolution.vote_count_pour = vote_count_pour;
376 resolution.vote_count_contre = vote_count_contre;
377 resolution.vote_count_abstention = vote_count_abstention;
378 resolution.total_voting_power_pour = total_voting_power_pour;
379 resolution.total_voting_power_contre = total_voting_power_contre;
380 resolution.total_voting_power_abstention = total_voting_power_abstention;
381 }
382 Ok(())
383 }
384
385 async fn close_voting(
386 &self,
387 resolution_id: Uuid,
388 final_status: ResolutionStatus,
389 ) -> Result<(), String> {
390 if let Some(resolution) = self.resolutions.lock().unwrap().get_mut(&resolution_id) {
391 resolution.status = final_status;
392 resolution.voted_at = Some(chrono::Utc::now());
393 }
394 Ok(())
395 }
396
397 async fn get_meeting_vote_summary(
398 &self,
399 meeting_id: Uuid,
400 ) -> Result<Vec<Resolution>, String> {
401 self.find_by_meeting_id(meeting_id).await
402 }
403 }
404
405 struct MockVoteRepository {
406 votes: Mutex<HashMap<Uuid, Vote>>,
407 }
408
409 impl MockVoteRepository {
410 fn new() -> Self {
411 Self {
412 votes: Mutex::new(HashMap::new()),
413 }
414 }
415 }
416
417 #[async_trait]
418 impl VoteRepository for MockVoteRepository {
419 async fn create(&self, vote: &Vote) -> Result<Vote, String> {
420 self.votes.lock().unwrap().insert(vote.id, vote.clone());
421 Ok(vote.clone())
422 }
423
424 async fn find_by_id(&self, id: Uuid) -> Result<Option<Vote>, String> {
425 Ok(self.votes.lock().unwrap().get(&id).cloned())
426 }
427
428 async fn find_by_resolution_id(&self, resolution_id: Uuid) -> Result<Vec<Vote>, String> {
429 Ok(self
430 .votes
431 .lock()
432 .unwrap()
433 .values()
434 .filter(|v| v.resolution_id == resolution_id)
435 .cloned()
436 .collect())
437 }
438
439 async fn find_by_owner_id(&self, owner_id: Uuid) -> Result<Vec<Vote>, String> {
440 Ok(self
441 .votes
442 .lock()
443 .unwrap()
444 .values()
445 .filter(|v| v.owner_id == owner_id)
446 .cloned()
447 .collect())
448 }
449
450 async fn find_by_resolution_and_unit(
451 &self,
452 resolution_id: Uuid,
453 unit_id: Uuid,
454 ) -> Result<Option<Vote>, String> {
455 Ok(self
456 .votes
457 .lock()
458 .unwrap()
459 .values()
460 .find(|v| v.resolution_id == resolution_id && v.unit_id == unit_id)
461 .cloned())
462 }
463
464 async fn has_voted(&self, resolution_id: Uuid, unit_id: Uuid) -> Result<bool, String> {
465 Ok(self
466 .find_by_resolution_and_unit(resolution_id, unit_id)
467 .await?
468 .is_some())
469 }
470
471 async fn update(&self, vote: &Vote) -> Result<Vote, String> {
472 self.votes.lock().unwrap().insert(vote.id, vote.clone());
473 Ok(vote.clone())
474 }
475
476 async fn delete(&self, id: Uuid) -> Result<bool, String> {
477 Ok(self.votes.lock().unwrap().remove(&id).is_some())
478 }
479
480 async fn count_by_resolution_and_choice(
481 &self,
482 resolution_id: Uuid,
483 ) -> Result<(i32, i32, i32), String> {
484 let votes = self.find_by_resolution_id(resolution_id).await?;
485 let pour = votes
486 .iter()
487 .filter(|v| v.vote_choice == VoteChoice::Pour)
488 .count() as i32;
489 let contre = votes
490 .iter()
491 .filter(|v| v.vote_choice == VoteChoice::Contre)
492 .count() as i32;
493 let abstention = votes
494 .iter()
495 .filter(|v| v.vote_choice == VoteChoice::Abstention)
496 .count() as i32;
497 Ok((pour, contre, abstention))
498 }
499
500 async fn sum_voting_power_by_resolution(
501 &self,
502 resolution_id: Uuid,
503 ) -> Result<(f64, f64, f64), String> {
504 let votes = self.find_by_resolution_id(resolution_id).await?;
505 let pour: f64 = votes
506 .iter()
507 .filter(|v| v.vote_choice == VoteChoice::Pour)
508 .map(|v| v.voting_power)
509 .sum();
510 let contre: f64 = votes
511 .iter()
512 .filter(|v| v.vote_choice == VoteChoice::Contre)
513 .map(|v| v.voting_power)
514 .sum();
515 let abstention: f64 = votes
516 .iter()
517 .filter(|v| v.vote_choice == VoteChoice::Abstention)
518 .map(|v| v.voting_power)
519 .sum();
520 Ok((pour, contre, abstention))
521 }
522 }
523
524 #[tokio::test]
525 async fn test_create_resolution() {
526 let resolution_repo = Arc::new(MockResolutionRepository::new());
527 let vote_repo = Arc::new(MockVoteRepository::new());
528 let use_cases = ResolutionUseCases::new(resolution_repo.clone(), vote_repo);
529
530 let meeting_id = Uuid::new_v4();
531 let result = use_cases
532 .create_resolution(
533 meeting_id,
534 "Test Resolution".to_string(),
535 "Description".to_string(),
536 ResolutionType::Ordinary,
537 MajorityType::Simple,
538 )
539 .await;
540
541 assert!(result.is_ok());
542 let resolution = result.unwrap();
543 assert_eq!(resolution.title, "Test Resolution");
544 assert_eq!(resolution.status, ResolutionStatus::Pending);
545 }
546
547 #[tokio::test]
548 async fn test_cast_vote_updates_counts() {
549 let resolution_repo = Arc::new(MockResolutionRepository::new());
550 let vote_repo = Arc::new(MockVoteRepository::new());
551 let use_cases = ResolutionUseCases::new(resolution_repo.clone(), vote_repo.clone());
552
553 let meeting_id = Uuid::new_v4();
555 let resolution = use_cases
556 .create_resolution(
557 meeting_id,
558 "Test Resolution".to_string(),
559 "Description".to_string(),
560 ResolutionType::Ordinary,
561 MajorityType::Simple,
562 )
563 .await
564 .unwrap();
565
566 let owner_id = Uuid::new_v4();
568 let unit_id = Uuid::new_v4();
569 let result = use_cases
570 .cast_vote(
571 resolution.id,
572 owner_id,
573 unit_id,
574 VoteChoice::Pour,
575 100.0,
576 None,
577 )
578 .await;
579
580 assert!(result.is_ok());
581
582 let updated_resolution = use_cases
584 .get_resolution(resolution.id)
585 .await
586 .unwrap()
587 .unwrap();
588 assert_eq!(updated_resolution.vote_count_pour, 1);
589 assert_eq!(updated_resolution.total_voting_power_pour, 100.0);
590 }
591
592 #[tokio::test]
593 async fn test_cannot_vote_twice() {
594 let resolution_repo = Arc::new(MockResolutionRepository::new());
595 let vote_repo = Arc::new(MockVoteRepository::new());
596 let use_cases = ResolutionUseCases::new(resolution_repo.clone(), vote_repo);
597
598 let meeting_id = Uuid::new_v4();
600 let resolution = use_cases
601 .create_resolution(
602 meeting_id,
603 "Test".to_string(),
604 "Desc".to_string(),
605 ResolutionType::Ordinary,
606 MajorityType::Simple,
607 )
608 .await
609 .unwrap();
610
611 let owner_id = Uuid::new_v4();
612 let unit_id = Uuid::new_v4();
613
614 let result1 = use_cases
616 .cast_vote(
617 resolution.id,
618 owner_id,
619 unit_id,
620 VoteChoice::Pour,
621 100.0,
622 None,
623 )
624 .await;
625 assert!(result1.is_ok());
626
627 let result2 = use_cases
629 .cast_vote(
630 resolution.id,
631 owner_id,
632 unit_id,
633 VoteChoice::Contre,
634 100.0,
635 None,
636 )
637 .await;
638 assert!(result2.is_err());
639 assert!(result2.unwrap_err().contains("already voted"));
640 }
641}