koprogo_api/application/dto/
document_dto.rs

1use crate::domain::entities::{Document, DocumentType};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6/// Response DTO for Document
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct DocumentResponse {
9    pub id: Uuid,
10    pub building_id: Uuid,
11    pub document_type: DocumentType,
12    pub title: String,
13    pub description: Option<String>,
14    pub file_path: String,
15    pub file_size: i64,
16    pub file_size_mb: f64,
17    pub mime_type: String,
18    pub uploaded_by: Uuid,
19    pub related_meeting_id: Option<Uuid>,
20    pub related_expense_id: Option<Uuid>,
21    pub created_at: DateTime<Utc>,
22    pub updated_at: DateTime<Utc>,
23}
24
25impl From<Document> for DocumentResponse {
26    fn from(doc: Document) -> Self {
27        let file_size_mb = doc.file_size_mb();
28        Self {
29            id: doc.id,
30            building_id: doc.building_id,
31            document_type: doc.document_type,
32            title: doc.title,
33            description: doc.description,
34            file_path: doc.file_path,
35            file_size: doc.file_size,
36            file_size_mb,
37            mime_type: doc.mime_type,
38            uploaded_by: doc.uploaded_by,
39            related_meeting_id: doc.related_meeting_id,
40            related_expense_id: doc.related_expense_id,
41            created_at: doc.created_at,
42            updated_at: doc.updated_at,
43        }
44    }
45}
46
47/// Request to upload a document
48/// Note: File upload is handled via multipart/form-data, this is for metadata
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct UploadDocumentRequest {
51    #[serde(default)]
52    pub organization_id: Uuid, // Will be overridden by JWT token
53    pub building_id: Uuid,
54    pub document_type: DocumentType,
55    pub title: String,
56    pub description: Option<String>,
57    pub uploaded_by: Uuid,
58    pub related_meeting_id: Option<Uuid>,
59    pub related_expense_id: Option<Uuid>,
60}
61
62/// Request to link document to meeting
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct LinkDocumentToMeetingRequest {
65    pub meeting_id: Uuid,
66}
67
68/// Request to link document to expense
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct LinkDocumentToExpenseRequest {
71    pub expense_id: Uuid,
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_document_response_from_entity() {
80        let org_id = Uuid::new_v4();
81        let building_id = Uuid::new_v4();
82        let uploader_id = Uuid::new_v4();
83
84        let document = Document::new(
85            org_id,
86            building_id,
87            DocumentType::Invoice,
88            "Test Invoice".to_string(),
89            Some("Test description".to_string()),
90            "/documents/test.pdf".to_string(),
91            1048576, // 1 MB
92            "application/pdf".to_string(),
93            uploader_id,
94        )
95        .unwrap();
96
97        let response = DocumentResponse::from(document.clone());
98
99        assert_eq!(response.id, document.id);
100        assert_eq!(response.title, "Test Invoice");
101        assert_eq!(response.file_size, 1048576);
102        assert_eq!(response.file_size_mb, 1.0);
103    }
104
105    #[test]
106    fn test_upload_request_serialization() {
107        let org_id = Uuid::new_v4();
108        let building_id = Uuid::new_v4();
109        let uploader_id = Uuid::new_v4();
110
111        let request = UploadDocumentRequest {
112            organization_id: org_id,
113            building_id,
114            document_type: DocumentType::Contract,
115            title: "Contrat de syndic".to_string(),
116            description: Some("Contrat annuel".to_string()),
117            uploaded_by: uploader_id,
118            related_meeting_id: None,
119            related_expense_id: None,
120        };
121
122        let json = serde_json::to_string(&request).unwrap();
123        assert!(json.contains("Contrat de syndic"));
124
125        let deserialized: UploadDocumentRequest = serde_json::from_str(&json).unwrap();
126        assert_eq!(deserialized.title, request.title);
127    }
128}