koprogo_api/domain/entities/
technical_inspection.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct TechnicalInspection {
11 pub id: Uuid,
12 pub organization_id: Uuid,
13 pub building_id: Uuid,
14
15 pub inspection_type: InspectionType,
17 pub title: String,
18 pub description: Option<String>,
19
20 pub inspector_name: String,
22 pub inspector_company: Option<String>,
23 pub inspector_certification: Option<String>, pub inspection_date: DateTime<Utc>,
27 pub next_due_date: DateTime<Utc>, pub status: InspectionStatus,
31 pub result_summary: Option<String>,
32 pub defects_found: Option<String>,
33 pub recommendations: Option<String>,
34
35 pub compliant: Option<bool>,
37 pub compliance_certificate_number: Option<String>,
38 pub compliance_valid_until: Option<DateTime<Utc>>,
39
40 pub cost: Option<f64>,
42 pub invoice_number: Option<String>,
43
44 pub reports: Vec<String>,
46 pub photos: Vec<String>,
47 pub certificates: Vec<String>,
48 pub notes: Option<String>,
49
50 pub created_at: DateTime<Utc>,
52 pub updated_at: DateTime<Utc>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
56#[serde(rename_all = "snake_case")]
57pub enum InspectionType {
58 Elevator, Boiler, Electrical, FireExtinguisher, FireAlarm, GasInstallation, RoofStructure, Facade, WaterQuality, Other { name: String }, }
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
71#[serde(rename_all = "snake_case")]
72pub enum InspectionStatus {
73 Scheduled, InProgress, Completed, Failed, Overdue, Cancelled, }
80
81impl InspectionType {
82 pub fn frequency_days(&self) -> i64 {
84 match self {
85 InspectionType::Elevator => 365, InspectionType::Boiler => 365, InspectionType::Electrical => 365 * 5, InspectionType::FireExtinguisher => 365, InspectionType::FireAlarm => 365, InspectionType::GasInstallation => 365, InspectionType::RoofStructure => 365 * 5, InspectionType::Facade => 365 * 5, InspectionType::WaterQuality => 365, InspectionType::Other { .. } => 365, }
96 }
97
98 pub fn display_name(&self) -> String {
100 match self {
101 InspectionType::Elevator => "Ascenseur".to_string(),
102 InspectionType::Boiler => "Chaudière".to_string(),
103 InspectionType::Electrical => "Installation électrique".to_string(),
104 InspectionType::FireExtinguisher => "Extincteurs".to_string(),
105 InspectionType::FireAlarm => "Alarme incendie".to_string(),
106 InspectionType::GasInstallation => "Installation gaz".to_string(),
107 InspectionType::RoofStructure => "Structure toiture".to_string(),
108 InspectionType::Facade => "Façade".to_string(),
109 InspectionType::WaterQuality => "Qualité de l'eau".to_string(),
110 InspectionType::Other { name } => name.clone(),
111 }
112 }
113}
114
115impl TechnicalInspection {
116 #[allow(clippy::too_many_arguments)]
117 pub fn new(
118 organization_id: Uuid,
119 building_id: Uuid,
120 title: String,
121 description: Option<String>,
122 inspection_type: InspectionType,
123 inspector_name: String,
124 inspection_date: DateTime<Utc>,
125 ) -> Self {
126 let now = Utc::now();
127
128 let next_due_date =
130 inspection_date + chrono::Duration::days(inspection_type.frequency_days());
131
132 Self {
133 id: Uuid::new_v4(),
134 organization_id,
135 building_id,
136 inspection_type,
137 title,
138 description,
139 inspector_name,
140 inspector_company: None,
141 inspector_certification: None,
142 inspection_date,
143 next_due_date,
144 status: InspectionStatus::Scheduled,
145 result_summary: None,
146 defects_found: None,
147 recommendations: None,
148 compliant: None,
149 compliance_certificate_number: None,
150 compliance_valid_until: None,
151 cost: None,
152 invoice_number: None,
153 reports: Vec::new(),
154 photos: Vec::new(),
155 certificates: Vec::new(),
156 notes: None,
157 created_at: now,
158 updated_at: now,
159 }
160 }
161
162 pub fn calculate_next_due_date(&self) -> DateTime<Utc> {
164 self.inspection_date + chrono::Duration::days(self.inspection_type.frequency_days())
165 }
166
167 pub fn is_overdue(&self) -> bool {
169 Utc::now() > self.next_due_date
170 }
171
172 pub fn days_until_due(&self) -> i64 {
174 (self.next_due_date - Utc::now()).num_days()
175 }
176
177 pub fn mark_overdue(&mut self) {
179 if self.is_overdue() && self.status == InspectionStatus::Scheduled {
180 self.status = InspectionStatus::Overdue;
181 self.updated_at = Utc::now();
182 }
183 }
184
185 pub fn add_report(&mut self, report_path: String) {
187 self.reports.push(report_path);
188 self.updated_at = Utc::now();
189 }
190
191 pub fn add_photo(&mut self, photo_path: String) {
193 self.photos.push(photo_path);
194 self.updated_at = Utc::now();
195 }
196
197 pub fn add_certificate(&mut self, certificate_path: String) {
199 self.certificates.push(certificate_path);
200 self.updated_at = Utc::now();
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_inspection_creation() {
210 let inspection = TechnicalInspection::new(
211 Uuid::new_v4(),
212 Uuid::new_v4(),
213 "Inspection annuelle ascenseur".to_string(),
214 Some("Vérification complète".to_string()),
215 InspectionType::Elevator,
216 "Schindler Belgium".to_string(),
217 Utc::now(),
218 );
219
220 assert_eq!(inspection.title, "Inspection annuelle ascenseur");
221 assert_eq!(inspection.status, InspectionStatus::Scheduled);
222 assert!(!inspection.is_overdue());
223 }
224
225 #[test]
226 fn test_inspection_frequencies() {
227 assert_eq!(InspectionType::Elevator.frequency_days(), 365);
228 assert_eq!(InspectionType::Electrical.frequency_days(), 365 * 5);
229 assert_eq!(InspectionType::Facade.frequency_days(), 365 * 5);
230 }
231
232 #[test]
233 fn test_inspection_completion() {
234 let mut inspection = TechnicalInspection::new(
235 Uuid::new_v4(),
236 Uuid::new_v4(),
237 "Inspection chaudière".to_string(),
238 None,
239 InspectionType::Boiler,
240 "Test Inspector".to_string(),
241 Utc::now(),
242 );
243
244 inspection.status = InspectionStatus::Completed;
245 inspection.compliant = Some(true);
246 assert_eq!(inspection.status, InspectionStatus::Completed);
247 assert_eq!(inspection.compliant, Some(true));
248 }
249
250 #[test]
251 fn test_overdue_detection() {
252 let past_date = Utc::now() - chrono::Duration::days(400); let mut inspection = TechnicalInspection::new(
254 Uuid::new_v4(),
255 Uuid::new_v4(),
256 "Test".to_string(),
257 None,
258 InspectionType::FireExtinguisher,
259 "Test".to_string(),
260 past_date,
261 );
262
263 assert!(inspection.is_overdue());
264 assert!(inspection.days_until_due() < 0);
265
266 inspection.mark_overdue();
267 assert_eq!(inspection.status, InspectionStatus::Overdue);
268 }
269}