koprogo_api/application/use_cases/
technical_inspection_use_cases.rs

1use crate::application::dto::{
2    AddCertificateDto, AddInspectionPhotoDto, AddReportDto, CreateTechnicalInspectionDto,
3    InspectionStatusDto, PageRequest, TechnicalInspectionFilters,
4    TechnicalInspectionListResponseDto, TechnicalInspectionResponseDto,
5    UpdateTechnicalInspectionDto,
6};
7use crate::application::ports::TechnicalInspectionRepository;
8use crate::domain::entities::{InspectionStatus, TechnicalInspection};
9use chrono::DateTime;
10use std::sync::Arc;
11use uuid::Uuid;
12
13pub struct TechnicalInspectionUseCases {
14    repository: Arc<dyn TechnicalInspectionRepository>,
15}
16
17impl TechnicalInspectionUseCases {
18    pub fn new(repository: Arc<dyn TechnicalInspectionRepository>) -> Self {
19        Self { repository }
20    }
21
22    pub async fn create_technical_inspection(
23        &self,
24        dto: CreateTechnicalInspectionDto,
25    ) -> Result<TechnicalInspectionResponseDto, String> {
26        let organization_id = Uuid::parse_str(&dto.organization_id)
27            .map_err(|_| "Invalid organization_id format".to_string())?;
28        let building_id = Uuid::parse_str(&dto.building_id)
29            .map_err(|_| "Invalid building_id format".to_string())?;
30
31        let inspection_date = DateTime::parse_from_rfc3339(&dto.inspection_date)
32            .map_err(|_| "Invalid inspection_date format".to_string())?
33            .with_timezone(&chrono::Utc);
34
35        let compliance_valid_until = if let Some(ref date_str) = dto.compliance_valid_until {
36            Some(
37                DateTime::parse_from_rfc3339(date_str)
38                    .map_err(|_| "Invalid compliance_valid_until format".to_string())?
39                    .with_timezone(&chrono::Utc),
40            )
41        } else {
42            None
43        };
44
45        let inspection = TechnicalInspection::new(
46            organization_id,
47            building_id,
48            dto.title,
49            dto.description,
50            dto.inspection_type.clone(),
51            dto.inspector_name,
52            inspection_date,
53        );
54
55        let mut inspection = inspection;
56        inspection.inspector_company = dto.inspector_company;
57        inspection.inspector_certification = dto.inspector_certification;
58        inspection.result_summary = dto.result_summary;
59        inspection.defects_found = dto.defects_found;
60        inspection.recommendations = dto.recommendations;
61        inspection.compliant = dto.compliant;
62        inspection.compliance_certificate_number = dto.compliance_certificate_number;
63        inspection.compliance_valid_until = compliance_valid_until;
64        inspection.cost = dto.cost;
65        inspection.invoice_number = dto.invoice_number;
66        inspection.notes = dto.notes;
67
68        let created = self.repository.create(&inspection).await?;
69        Ok(self.to_response_dto(&created))
70    }
71
72    pub async fn get_technical_inspection(
73        &self,
74        id: Uuid,
75    ) -> Result<Option<TechnicalInspectionResponseDto>, String> {
76        let inspection = self.repository.find_by_id(id).await?;
77        Ok(inspection.map(|i| self.to_response_dto(&i)))
78    }
79
80    pub async fn list_technical_inspections_by_building(
81        &self,
82        building_id: Uuid,
83    ) -> Result<Vec<TechnicalInspectionResponseDto>, String> {
84        let inspections = self.repository.find_by_building(building_id).await?;
85        Ok(inspections
86            .iter()
87            .map(|i| self.to_response_dto(i))
88            .collect())
89    }
90
91    pub async fn list_technical_inspections_by_organization(
92        &self,
93        organization_id: Uuid,
94    ) -> Result<Vec<TechnicalInspectionResponseDto>, String> {
95        let inspections = self
96            .repository
97            .find_by_organization(organization_id)
98            .await?;
99        Ok(inspections
100            .iter()
101            .map(|i| self.to_response_dto(i))
102            .collect())
103    }
104
105    pub async fn list_technical_inspections_paginated(
106        &self,
107        page_request: &PageRequest,
108        filters: &TechnicalInspectionFilters,
109    ) -> Result<TechnicalInspectionListResponseDto, String> {
110        let (inspections, total) = self
111            .repository
112            .find_all_paginated(page_request, filters)
113            .await?;
114
115        let dtos = inspections
116            .iter()
117            .map(|i| self.to_response_dto(i))
118            .collect();
119
120        Ok(TechnicalInspectionListResponseDto {
121            inspections: dtos,
122            total,
123            page: page_request.page,
124            page_size: page_request.per_page,
125        })
126    }
127
128    pub async fn get_overdue_inspections(
129        &self,
130        building_id: Uuid,
131    ) -> Result<Vec<InspectionStatusDto>, String> {
132        let inspections = self.repository.find_overdue(building_id).await?;
133
134        Ok(inspections
135            .iter()
136            .map(|i| InspectionStatusDto {
137                inspection_id: i.id.to_string(),
138                title: i.title.clone(),
139                inspection_type: i.inspection_type.clone(),
140                next_due_date: i.next_due_date.to_rfc3339(),
141                status: i.status.clone(),
142                is_overdue: i.is_overdue(),
143                days_until_due: i.days_until_due(),
144            })
145            .collect())
146    }
147
148    pub async fn get_upcoming_inspections(
149        &self,
150        building_id: Uuid,
151        days: i32,
152    ) -> Result<Vec<InspectionStatusDto>, String> {
153        let inspections = self.repository.find_upcoming(building_id, days).await?;
154
155        Ok(inspections
156            .iter()
157            .map(|i| InspectionStatusDto {
158                inspection_id: i.id.to_string(),
159                title: i.title.clone(),
160                inspection_type: i.inspection_type.clone(),
161                next_due_date: i.next_due_date.to_rfc3339(),
162                status: i.status.clone(),
163                is_overdue: i.is_overdue(),
164                days_until_due: i.days_until_due(),
165            })
166            .collect())
167    }
168
169    pub async fn get_inspections_by_type(
170        &self,
171        building_id: Uuid,
172        inspection_type: &str,
173    ) -> Result<Vec<TechnicalInspectionResponseDto>, String> {
174        let inspections = self
175            .repository
176            .find_by_type(building_id, inspection_type)
177            .await?;
178
179        Ok(inspections
180            .iter()
181            .map(|i| self.to_response_dto(i))
182            .collect())
183    }
184
185    pub async fn update_technical_inspection(
186        &self,
187        id: Uuid,
188        dto: UpdateTechnicalInspectionDto,
189    ) -> Result<TechnicalInspectionResponseDto, String> {
190        let mut inspection = self
191            .repository
192            .find_by_id(id)
193            .await?
194            .ok_or_else(|| "Technical inspection not found".to_string())?;
195
196        if let Some(title) = dto.title {
197            inspection.title = title;
198        }
199        if let Some(description) = dto.description {
200            inspection.description = Some(description);
201        }
202        if let Some(inspection_type) = dto.inspection_type {
203            inspection.inspection_type = inspection_type;
204            // Recalculate next_due_date when type changes
205            inspection.next_due_date = inspection.calculate_next_due_date();
206        }
207        if let Some(inspector_name) = dto.inspector_name {
208            inspection.inspector_name = inspector_name;
209        }
210        if let Some(inspector_company) = dto.inspector_company {
211            inspection.inspector_company = Some(inspector_company);
212        }
213        if let Some(inspector_certification) = dto.inspector_certification {
214            inspection.inspector_certification = Some(inspector_certification);
215        }
216        if let Some(inspection_date_str) = dto.inspection_date {
217            let inspection_date = DateTime::parse_from_rfc3339(&inspection_date_str)
218                .map_err(|_| "Invalid inspection_date format".to_string())?
219                .with_timezone(&chrono::Utc);
220            inspection.inspection_date = inspection_date;
221            // Recalculate next_due_date when date changes
222            inspection.next_due_date = inspection.calculate_next_due_date();
223        }
224        if let Some(status) = dto.status {
225            inspection.status = status;
226        }
227        if let Some(result_summary) = dto.result_summary {
228            inspection.result_summary = Some(result_summary);
229        }
230        if let Some(defects_found) = dto.defects_found {
231            inspection.defects_found = Some(defects_found);
232        }
233        if let Some(recommendations) = dto.recommendations {
234            inspection.recommendations = Some(recommendations);
235        }
236        if let Some(compliant) = dto.compliant {
237            inspection.compliant = Some(compliant);
238        }
239        if let Some(compliance_certificate_number) = dto.compliance_certificate_number {
240            inspection.compliance_certificate_number = Some(compliance_certificate_number);
241        }
242        if let Some(compliance_valid_until_str) = dto.compliance_valid_until {
243            let compliance_valid_until = DateTime::parse_from_rfc3339(&compliance_valid_until_str)
244                .map_err(|_| "Invalid compliance_valid_until format".to_string())?
245                .with_timezone(&chrono::Utc);
246            inspection.compliance_valid_until = Some(compliance_valid_until);
247        }
248        if let Some(cost) = dto.cost {
249            inspection.cost = Some(cost);
250        }
251        if let Some(invoice_number) = dto.invoice_number {
252            inspection.invoice_number = Some(invoice_number);
253        }
254        if let Some(notes) = dto.notes {
255            inspection.notes = Some(notes);
256        }
257
258        inspection.updated_at = chrono::Utc::now();
259
260        let updated = self.repository.update(&inspection).await?;
261        Ok(self.to_response_dto(&updated))
262    }
263
264    pub async fn mark_as_completed(
265        &self,
266        id: Uuid,
267    ) -> Result<TechnicalInspectionResponseDto, String> {
268        let mut inspection = self
269            .repository
270            .find_by_id(id)
271            .await?
272            .ok_or_else(|| "Technical inspection not found".to_string())?;
273
274        inspection.status = InspectionStatus::Completed;
275        inspection.updated_at = chrono::Utc::now();
276
277        let updated = self.repository.update(&inspection).await?;
278        Ok(self.to_response_dto(&updated))
279    }
280
281    pub async fn add_report(
282        &self,
283        id: Uuid,
284        dto: AddReportDto,
285    ) -> Result<TechnicalInspectionResponseDto, String> {
286        let mut inspection = self
287            .repository
288            .find_by_id(id)
289            .await?
290            .ok_or_else(|| "Technical inspection not found".to_string())?;
291
292        inspection.add_report(dto.report_path);
293
294        let updated = self.repository.update(&inspection).await?;
295        Ok(self.to_response_dto(&updated))
296    }
297
298    pub async fn add_photo(
299        &self,
300        id: Uuid,
301        dto: AddInspectionPhotoDto,
302    ) -> Result<TechnicalInspectionResponseDto, String> {
303        let mut inspection = self
304            .repository
305            .find_by_id(id)
306            .await?
307            .ok_or_else(|| "Technical inspection not found".to_string())?;
308
309        inspection.add_photo(dto.photo_path);
310
311        let updated = self.repository.update(&inspection).await?;
312        Ok(self.to_response_dto(&updated))
313    }
314
315    pub async fn add_certificate(
316        &self,
317        id: Uuid,
318        dto: AddCertificateDto,
319    ) -> Result<TechnicalInspectionResponseDto, String> {
320        let mut inspection = self
321            .repository
322            .find_by_id(id)
323            .await?
324            .ok_or_else(|| "Technical inspection not found".to_string())?;
325
326        inspection.add_certificate(dto.certificate_path);
327
328        let updated = self.repository.update(&inspection).await?;
329        Ok(self.to_response_dto(&updated))
330    }
331
332    pub async fn delete_technical_inspection(&self, id: Uuid) -> Result<bool, String> {
333        self.repository.delete(id).await
334    }
335
336    fn to_response_dto(&self, inspection: &TechnicalInspection) -> TechnicalInspectionResponseDto {
337        TechnicalInspectionResponseDto {
338            id: inspection.id.to_string(),
339            organization_id: inspection.organization_id.to_string(),
340            building_id: inspection.building_id.to_string(),
341            title: inspection.title.clone(),
342            description: inspection.description.clone(),
343            inspection_type: inspection.inspection_type.clone(),
344            inspector_name: inspection.inspector_name.clone(),
345            inspector_company: inspection.inspector_company.clone(),
346            inspector_certification: inspection.inspector_certification.clone(),
347            inspection_date: inspection.inspection_date.to_rfc3339(),
348            next_due_date: inspection.next_due_date.to_rfc3339(),
349            status: inspection.status.clone(),
350            result_summary: inspection.result_summary.clone(),
351            defects_found: inspection.defects_found.clone(),
352            recommendations: inspection.recommendations.clone(),
353            compliant: inspection.compliant,
354            compliance_certificate_number: inspection.compliance_certificate_number.clone(),
355            compliance_valid_until: inspection
356                .compliance_valid_until
357                .as_ref()
358                .map(|d| d.to_rfc3339()),
359            cost: inspection.cost,
360            invoice_number: inspection.invoice_number.clone(),
361            reports: inspection.reports.clone(),
362            photos: inspection.photos.clone(),
363            certificates: inspection.certificates.clone(),
364            notes: inspection.notes.clone(),
365            is_overdue: inspection.is_overdue(),
366            days_until_due: inspection.days_until_due(),
367            created_at: inspection.created_at.to_rfc3339(),
368            updated_at: inspection.updated_at.to_rfc3339(),
369        }
370    }
371}