koprogo_api/domain/entities/
document.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub enum DocumentType {
8 MeetingMinutes, FinancialStatement, Invoice, Contract, Regulation, WorksQuote, Other,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19pub struct Document {
20 pub id: Uuid,
21 pub organization_id: Uuid,
22 pub building_id: Uuid,
23 pub document_type: DocumentType,
24 pub title: String,
25 pub description: Option<String>,
26 pub file_path: String,
27 pub file_size: i64, pub mime_type: String,
29 pub uploaded_by: Uuid, pub related_meeting_id: Option<Uuid>,
31 pub related_expense_id: Option<Uuid>,
32 pub created_at: DateTime<Utc>,
33 pub updated_at: DateTime<Utc>,
34}
35
36impl Document {
37 #[allow(clippy::too_many_arguments)]
38 pub fn new(
39 organization_id: Uuid,
40 building_id: Uuid,
41 document_type: DocumentType,
42 title: String,
43 description: Option<String>,
44 file_path: String,
45 file_size: i64,
46 mime_type: String,
47 uploaded_by: Uuid,
48 ) -> Result<Self, String> {
49 if title.is_empty() {
50 return Err("Title cannot be empty".to_string());
51 }
52 if file_path.is_empty() {
53 return Err("File path cannot be empty".to_string());
54 }
55 if file_size <= 0 {
56 return Err("File size must be greater than 0".to_string());
57 }
58
59 let now = Utc::now();
60 Ok(Self {
61 id: Uuid::new_v4(),
62 organization_id,
63 building_id,
64 document_type,
65 title,
66 description,
67 file_path,
68 file_size,
69 mime_type,
70 uploaded_by,
71 related_meeting_id: None,
72 related_expense_id: None,
73 created_at: now,
74 updated_at: now,
75 })
76 }
77
78 pub fn link_to_meeting(&mut self, meeting_id: Uuid) {
79 self.related_meeting_id = Some(meeting_id);
80 self.updated_at = Utc::now();
81 }
82
83 pub fn link_to_expense(&mut self, expense_id: Uuid) {
84 self.related_expense_id = Some(expense_id);
85 self.updated_at = Utc::now();
86 }
87
88 pub fn file_size_mb(&self) -> f64 {
89 self.file_size as f64 / 1_048_576.0 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_create_document_success() {
99 let org_id = Uuid::new_v4();
100 let building_id = Uuid::new_v4();
101 let uploader_id = Uuid::new_v4();
102
103 let document = Document::new(
104 org_id,
105 building_id,
106 DocumentType::MeetingMinutes,
107 "PV AGO 2024".to_string(),
108 Some("Procès-verbal de l'assemblée générale ordinaire 2024".to_string()),
109 "/documents/pv-ago-2024.pdf".to_string(),
110 1048576, "application/pdf".to_string(),
112 uploader_id,
113 );
114
115 assert!(document.is_ok());
116 let document = document.unwrap();
117 assert_eq!(document.organization_id, org_id);
118 assert_eq!(document.file_size_mb(), 1.0);
119 }
120
121 #[test]
122 fn test_create_document_empty_title_fails() {
123 let org_id = Uuid::new_v4();
124 let building_id = Uuid::new_v4();
125 let uploader_id = Uuid::new_v4();
126
127 let document = Document::new(
128 org_id,
129 building_id,
130 DocumentType::Invoice,
131 "".to_string(),
132 None,
133 "/documents/test.pdf".to_string(),
134 1024,
135 "application/pdf".to_string(),
136 uploader_id,
137 );
138
139 assert!(document.is_err());
140 }
141
142 #[test]
143 fn test_link_document_to_meeting() {
144 let org_id = Uuid::new_v4();
145 let building_id = Uuid::new_v4();
146 let uploader_id = Uuid::new_v4();
147
148 let mut document = Document::new(
149 org_id,
150 building_id,
151 DocumentType::MeetingMinutes,
152 "Test".to_string(),
153 None,
154 "/test.pdf".to_string(),
155 1024,
156 "application/pdf".to_string(),
157 uploader_id,
158 )
159 .unwrap();
160
161 let meeting_id = Uuid::new_v4();
162 document.link_to_meeting(meeting_id);
163
164 assert_eq!(document.related_meeting_id, Some(meeting_id));
165 }
166}