koprogo_api/domain/entities/
document.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// Type de document
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub enum DocumentType {
8    MeetingMinutes,     // Procès-verbal
9    FinancialStatement, // Bilan financier
10    Invoice,            // Facture
11    Contract,           // Contrat
12    Regulation,         // Règlement
13    WorksQuote,         // Devis travaux
14    Other,
15}
16
17/// Représente un document de copropriété
18#[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, // en bytes
28    pub mime_type: String,
29    pub uploaded_by: Uuid, // ID de l'utilisateur qui a uploadé
30    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 // Convertir bytes en MB
90    }
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, // 1 MB
111            "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}