koprogo_api/domain/entities/
meeting.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub enum MeetingType {
8 Ordinary, Extraordinary, }
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub enum MeetingStatus {
15 Scheduled,
16 Completed,
17 Cancelled,
18}
19
20#[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) -> Result<(), String> {
83 match self.status {
84 MeetingStatus::Scheduled => {
85 self.status = MeetingStatus::Completed;
86 self.attendees_count = Some(attendees_count);
87 self.updated_at = Utc::now();
88 Ok(())
89 }
90 MeetingStatus::Completed => Err("Meeting is already completed".to_string()),
91 MeetingStatus::Cancelled => Err("Cannot complete a cancelled meeting".to_string()),
92 }
93 }
94
95 pub fn cancel(&mut self) -> Result<(), String> {
96 match self.status {
97 MeetingStatus::Scheduled => {
98 self.status = MeetingStatus::Cancelled;
99 self.updated_at = Utc::now();
100 Ok(())
101 }
102 MeetingStatus::Completed => Err("Cannot cancel a completed meeting".to_string()),
103 MeetingStatus::Cancelled => Err("Meeting is already cancelled".to_string()),
104 }
105 }
106
107 pub fn reschedule(&mut self, new_date: DateTime<Utc>) -> Result<(), String> {
108 match self.status {
109 MeetingStatus::Scheduled | MeetingStatus::Cancelled => {
110 self.scheduled_date = new_date;
111 self.status = MeetingStatus::Scheduled;
112 self.updated_at = Utc::now();
113 Ok(())
114 }
115 MeetingStatus::Completed => Err("Cannot reschedule a completed meeting".to_string()),
116 }
117 }
118
119 pub fn is_upcoming(&self) -> bool {
120 self.status == MeetingStatus::Scheduled && self.scheduled_date > Utc::now()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use chrono::Duration;
128
129 #[test]
130 fn test_create_meeting_success() {
131 let org_id = Uuid::new_v4();
132 let building_id = Uuid::new_v4();
133 let future_date = Utc::now() + Duration::days(30);
134
135 let meeting = Meeting::new(
136 org_id,
137 building_id,
138 MeetingType::Ordinary,
139 "AGO 2024".to_string(),
140 Some("Assemblée générale ordinaire annuelle".to_string()),
141 future_date,
142 "Salle des fêtes".to_string(),
143 );
144
145 assert!(meeting.is_ok());
146 let meeting = meeting.unwrap();
147 assert_eq!(meeting.organization_id, org_id);
148 assert_eq!(meeting.status, MeetingStatus::Scheduled);
149 assert!(meeting.is_upcoming());
150 }
151
152 #[test]
153 fn test_add_agenda_item() {
154 let org_id = Uuid::new_v4();
155 let building_id = Uuid::new_v4();
156 let future_date = Utc::now() + Duration::days(30);
157
158 let mut meeting = Meeting::new(
159 org_id,
160 building_id,
161 MeetingType::Ordinary,
162 "AGO 2024".to_string(),
163 None,
164 future_date,
165 "Salle des fêtes".to_string(),
166 )
167 .unwrap();
168
169 let result = meeting.add_agenda_item("Approbation des comptes".to_string());
170 assert!(result.is_ok());
171 assert_eq!(meeting.agenda.len(), 1);
172 }
173
174 #[test]
175 fn test_complete_meeting() {
176 let org_id = Uuid::new_v4();
177 let building_id = Uuid::new_v4();
178 let future_date = Utc::now() + Duration::days(30);
179
180 let mut meeting = Meeting::new(
181 org_id,
182 building_id,
183 MeetingType::Ordinary,
184 "AGO 2024".to_string(),
185 None,
186 future_date,
187 "Salle des fêtes".to_string(),
188 )
189 .unwrap();
190
191 let result = meeting.complete(45);
192 assert!(result.is_ok());
193 assert_eq!(meeting.status, MeetingStatus::Completed);
194 assert_eq!(meeting.attendees_count, Some(45));
195 assert!(!meeting.is_upcoming());
196 }
197
198 #[test]
199 fn test_complete_already_completed_fails() {
200 let org_id = Uuid::new_v4();
201 let building_id = Uuid::new_v4();
202 let future_date = Utc::now() + Duration::days(30);
203
204 let mut meeting = Meeting::new(
205 org_id,
206 building_id,
207 MeetingType::Ordinary,
208 "AGO 2024".to_string(),
209 None,
210 future_date,
211 "Salle des fêtes".to_string(),
212 )
213 .unwrap();
214
215 meeting.complete(45).unwrap();
216 let result = meeting.complete(50);
217 assert!(result.is_err());
218 assert_eq!(meeting.attendees_count, Some(45)); }
220
221 #[test]
222 fn test_cancel_meeting() {
223 let org_id = Uuid::new_v4();
224 let building_id = Uuid::new_v4();
225 let future_date = Utc::now() + Duration::days(30);
226
227 let mut meeting = Meeting::new(
228 org_id,
229 building_id,
230 MeetingType::Ordinary,
231 "AGO 2024".to_string(),
232 None,
233 future_date,
234 "Salle des fêtes".to_string(),
235 )
236 .unwrap();
237
238 let result = meeting.cancel();
239 assert!(result.is_ok());
240 assert_eq!(meeting.status, MeetingStatus::Cancelled);
241 }
242
243 #[test]
244 fn test_reschedule_meeting() {
245 let org_id = Uuid::new_v4();
246 let building_id = Uuid::new_v4();
247 let future_date = Utc::now() + Duration::days(30);
248
249 let mut meeting = Meeting::new(
250 org_id,
251 building_id,
252 MeetingType::Ordinary,
253 "AGO 2024".to_string(),
254 None,
255 future_date,
256 "Salle des fêtes".to_string(),
257 )
258 .unwrap();
259
260 let new_date = Utc::now() + Duration::days(60);
261 let result = meeting.reschedule(new_date);
262 assert!(result.is_ok());
263 assert_eq!(meeting.scheduled_date, new_date);
264 }
265}