koprogo_api/domain/entities/
meeting.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// Type d'assemblée générale
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub enum MeetingType {
8    Ordinary,      // Assemblée Générale Ordinaire (AGO)
9    Extraordinary, // Assemblée Générale Extraordinaire (AGE)
10}
11
12/// Statut de l'assemblée
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub enum MeetingStatus {
15    Scheduled,
16    Completed,
17    Cancelled,
18}
19
20/// Représente une assemblée générale de copropriétaires
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22pub struct Meeting {
23    pub id: Uuid,
24    pub organization_id: Uuid,
25    pub building_id: Uuid,
26    pub meeting_type: MeetingType,
27    pub title: String,
28    pub description: Option<String>,
29    pub scheduled_date: DateTime<Utc>,
30    pub location: String,
31    pub status: MeetingStatus,
32    pub agenda: Vec<String>,
33    pub attendees_count: Option<i32>,
34    pub created_at: DateTime<Utc>,
35    pub updated_at: DateTime<Utc>,
36}
37
38impl Meeting {
39    pub fn new(
40        organization_id: Uuid,
41        building_id: Uuid,
42        meeting_type: MeetingType,
43        title: String,
44        description: Option<String>,
45        scheduled_date: DateTime<Utc>,
46        location: String,
47    ) -> Result<Self, String> {
48        if title.is_empty() {
49            return Err("Title cannot be empty".to_string());
50        }
51        if location.is_empty() {
52            return Err("Location cannot be empty".to_string());
53        }
54
55        let now = Utc::now();
56        Ok(Self {
57            id: Uuid::new_v4(),
58            organization_id,
59            building_id,
60            meeting_type,
61            title,
62            description,
63            scheduled_date,
64            location,
65            status: MeetingStatus::Scheduled,
66            agenda: Vec::new(),
67            attendees_count: None,
68            created_at: now,
69            updated_at: now,
70        })
71    }
72
73    pub fn add_agenda_item(&mut self, item: String) -> Result<(), String> {
74        if item.is_empty() {
75            return Err("Agenda item cannot be empty".to_string());
76        }
77        self.agenda.push(item);
78        self.updated_at = Utc::now();
79        Ok(())
80    }
81
82    pub fn complete(&mut self, attendees_count: i32) {
83        self.status = MeetingStatus::Completed;
84        self.attendees_count = Some(attendees_count);
85        self.updated_at = Utc::now();
86    }
87
88    pub fn cancel(&mut self) {
89        self.status = MeetingStatus::Cancelled;
90        self.updated_at = Utc::now();
91    }
92
93    pub fn is_upcoming(&self) -> bool {
94        self.status == MeetingStatus::Scheduled && self.scheduled_date > Utc::now()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use chrono::Duration;
102
103    #[test]
104    fn test_create_meeting_success() {
105        let org_id = Uuid::new_v4();
106        let building_id = Uuid::new_v4();
107        let future_date = Utc::now() + Duration::days(30);
108
109        let meeting = Meeting::new(
110            org_id,
111            building_id,
112            MeetingType::Ordinary,
113            "AGO 2024".to_string(),
114            Some("Assemblée générale ordinaire annuelle".to_string()),
115            future_date,
116            "Salle des fêtes".to_string(),
117        );
118
119        assert!(meeting.is_ok());
120        let meeting = meeting.unwrap();
121        assert_eq!(meeting.organization_id, org_id);
122        assert_eq!(meeting.status, MeetingStatus::Scheduled);
123        assert!(meeting.is_upcoming());
124    }
125
126    #[test]
127    fn test_add_agenda_item() {
128        let org_id = Uuid::new_v4();
129        let building_id = Uuid::new_v4();
130        let future_date = Utc::now() + Duration::days(30);
131
132        let mut meeting = Meeting::new(
133            org_id,
134            building_id,
135            MeetingType::Ordinary,
136            "AGO 2024".to_string(),
137            None,
138            future_date,
139            "Salle des fêtes".to_string(),
140        )
141        .unwrap();
142
143        let result = meeting.add_agenda_item("Approbation des comptes".to_string());
144        assert!(result.is_ok());
145        assert_eq!(meeting.agenda.len(), 1);
146    }
147
148    #[test]
149    fn test_complete_meeting() {
150        let org_id = Uuid::new_v4();
151        let building_id = Uuid::new_v4();
152        let future_date = Utc::now() + Duration::days(30);
153
154        let mut meeting = Meeting::new(
155            org_id,
156            building_id,
157            MeetingType::Ordinary,
158            "AGO 2024".to_string(),
159            None,
160            future_date,
161            "Salle des fêtes".to_string(),
162        )
163        .unwrap();
164
165        meeting.complete(45);
166        assert_eq!(meeting.status, MeetingStatus::Completed);
167        assert_eq!(meeting.attendees_count, Some(45));
168        assert!(!meeting.is_upcoming());
169    }
170}