koprogo_api/domain/entities/
work_report.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// Work Report - Rapport de travaux effectués
6///
7/// Tracks maintenance work, repairs, and renovations performed on the building.
8/// Part of the digital maintenance logbook (Carnet d'Entretien).
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct WorkReport {
11    pub id: Uuid,
12    pub organization_id: Uuid,
13    pub building_id: Uuid,
14
15    // Work details
16    pub title: String,
17    pub description: String,
18    pub work_type: WorkType,
19    pub contractor_name: String,
20    pub contractor_contact: Option<String>,
21
22    // Dates
23    pub work_date: DateTime<Utc>,               // Date of work
24    pub completion_date: Option<DateTime<Utc>>, // If different from work_date
25
26    // Financial
27    pub cost: f64,
28    pub invoice_number: Option<String>,
29
30    // Documentation
31    pub photos: Vec<String>,    // File paths to photos
32    pub documents: Vec<String>, // File paths to related documents
33    pub notes: Option<String>,
34
35    // Warranty tracking
36    pub warranty_type: WarrantyType,
37    pub warranty_expiry: DateTime<Utc>,
38
39    // Metadata
40    pub created_at: DateTime<Utc>,
41    pub updated_at: DateTime<Utc>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45#[serde(rename_all = "snake_case")]
46pub enum WorkType {
47    Maintenance,  // Entretien régulier
48    Repair,       // Réparation
49    Renovation,   // Rénovation
50    Emergency,    // Intervention d'urgence
51    Inspection,   // Inspection avec travaux
52    Installation, // Installation nouvel équipement
53    Other,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
57#[serde(rename_all = "snake_case")]
58pub enum WarrantyType {
59    None,                  // Pas de garantie
60    Standard,              // 2 ans (vices apparents)
61    Decennial,             // 10 ans (garantie décennale)
62    Extended,              // Garantie étendue (matériel)
63    Custom { years: i32 }, // Garantie personnalisée
64}
65
66impl WorkReport {
67    #[allow(clippy::too_many_arguments)]
68    pub fn new(
69        organization_id: Uuid,
70        building_id: Uuid,
71        title: String,
72        description: String,
73        work_type: WorkType,
74        contractor_name: String,
75        work_date: DateTime<Utc>,
76        cost: f64,
77        warranty_type: WarrantyType,
78    ) -> Self {
79        let now = Utc::now();
80
81        // Calculate warranty expiry based on type
82        let warranty_expiry = match warranty_type {
83            WarrantyType::None => now, // No warranty
84            WarrantyType::Standard => work_date + chrono::Duration::days(2 * 365), // 2 years
85            WarrantyType::Decennial => work_date + chrono::Duration::days(10 * 365), // 10 years
86            WarrantyType::Extended => work_date + chrono::Duration::days(3 * 365), // 3 years default
87            WarrantyType::Custom { years } => {
88                work_date + chrono::Duration::days(years as i64 * 365)
89            }
90        };
91
92        Self {
93            id: Uuid::new_v4(),
94            organization_id,
95            building_id,
96            title,
97            description,
98            work_type,
99            contractor_name,
100            contractor_contact: None,
101            work_date,
102            completion_date: None,
103            cost,
104            invoice_number: None,
105            photos: Vec::new(),
106            documents: Vec::new(),
107            notes: None,
108            warranty_type,
109            warranty_expiry,
110            created_at: now,
111            updated_at: now,
112        }
113    }
114
115    /// Check if warranty is still valid
116    pub fn is_warranty_valid(&self) -> bool {
117        Utc::now() < self.warranty_expiry
118    }
119
120    /// Get remaining warranty days
121    pub fn warranty_days_remaining(&self) -> i64 {
122        let now = Utc::now();
123        if now >= self.warranty_expiry {
124            0
125        } else {
126            (self.warranty_expiry - now).num_days()
127        }
128    }
129
130    /// Add photo to work report
131    pub fn add_photo(&mut self, photo_path: String) {
132        self.photos.push(photo_path);
133        self.updated_at = Utc::now();
134    }
135
136    /// Add document to work report
137    pub fn add_document(&mut self, document_path: String) {
138        self.documents.push(document_path);
139        self.updated_at = Utc::now();
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_work_report_creation() {
149        let report = WorkReport::new(
150            Uuid::new_v4(),
151            Uuid::new_v4(),
152            "Réparation ascenseur".to_string(),
153            "Remplacement câble principal".to_string(),
154            WorkType::Repair,
155            "Schindler Belgium".to_string(),
156            Utc::now(),
157            1500.0,
158            WarrantyType::Standard,
159        );
160
161        assert_eq!(report.title, "Réparation ascenseur");
162        assert_eq!(report.cost, 1500.0);
163        assert!(report.is_warranty_valid());
164        assert!(report.warranty_days_remaining() > 700); // ~2 years
165    }
166
167    #[test]
168    fn test_decennial_warranty() {
169        let report = WorkReport::new(
170            Uuid::new_v4(),
171            Uuid::new_v4(),
172            "Rénovation façade".to_string(),
173            "Réfection complète façade".to_string(),
174            WorkType::Renovation,
175            "BatiPro SPRL".to_string(),
176            Utc::now(),
177            50000.0,
178            WarrantyType::Decennial,
179        );
180
181        assert!(report.warranty_days_remaining() > 3600); // ~10 years
182    }
183
184    #[test]
185    fn test_add_photos() {
186        let mut report = WorkReport::new(
187            Uuid::new_v4(),
188            Uuid::new_v4(),
189            "Test".to_string(),
190            "Test".to_string(),
191            WorkType::Maintenance,
192            "Test".to_string(),
193            Utc::now(),
194            100.0,
195            WarrantyType::None,
196        );
197
198        report.add_photo("/uploads/photo1.jpg".to_string());
199        report.add_photo("/uploads/photo2.jpg".to_string());
200
201        assert_eq!(report.photos.len(), 2);
202    }
203}