koprogo_api/infrastructure/web/handlers/
work_report_handlers.rs

1use crate::application::dto::{
2    AddDocumentDto, AddPhotoDto, CreateWorkReportDto, PageRequest, UpdateWorkReportDto,
3    WorkReportFilters,
4};
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use crate::infrastructure::web::{AppState, AuthenticatedUser};
7use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
8use uuid::Uuid;
9
10// ==================== Work Report CRUD Endpoints ====================
11
12/// Create a new work report
13#[post("/work-reports")]
14pub async fn create_work_report(
15    state: web::Data<AppState>,
16    user: AuthenticatedUser,
17    request: web::Json<CreateWorkReportDto>,
18) -> impl Responder {
19    let organization_id = match user.require_organization() {
20        Ok(org_id) => org_id,
21        Err(e) => {
22            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
23        }
24    };
25
26    match state
27        .work_report_use_cases
28        .create_work_report(request.into_inner())
29        .await
30    {
31        Ok(work_report) => {
32            AuditLogEntry::new(
33                AuditEventType::WorkReportCreated,
34                Some(user.user_id),
35                Some(organization_id),
36            )
37            .with_resource("WorkReport", work_report.id.parse().unwrap())
38            .log();
39
40            HttpResponse::Created().json(work_report)
41        }
42        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
43    }
44}
45
46/// Get work report by ID
47#[get("/work-reports/{id}")]
48pub async fn get_work_report(state: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
49    match state.work_report_use_cases.get_work_report(*id).await {
50        Ok(Some(work_report)) => HttpResponse::Ok().json(work_report),
51        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
52            "error": "Work report not found"
53        })),
54        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
55    }
56}
57
58/// List work reports by building
59#[get("/buildings/{building_id}/work-reports")]
60pub async fn list_building_work_reports(
61    state: web::Data<AppState>,
62    building_id: web::Path<Uuid>,
63) -> impl Responder {
64    match state
65        .work_report_use_cases
66        .list_work_reports_by_building(*building_id)
67        .await
68    {
69        Ok(work_reports) => HttpResponse::Ok().json(work_reports),
70        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
71    }
72}
73
74/// List work reports by organization
75#[get("/organizations/{organization_id}/work-reports")]
76pub async fn list_organization_work_reports(
77    state: web::Data<AppState>,
78    organization_id: web::Path<Uuid>,
79) -> impl Responder {
80    match state
81        .work_report_use_cases
82        .list_work_reports_by_organization(*organization_id)
83        .await
84    {
85        Ok(work_reports) => HttpResponse::Ok().json(work_reports),
86        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
87    }
88}
89
90/// List work reports with pagination and filters
91#[get("/work-reports")]
92pub async fn list_work_reports_paginated(
93    state: web::Data<AppState>,
94    page_request: web::Query<PageRequest>,
95    filters: web::Query<WorkReportFilters>,
96) -> impl Responder {
97    match state
98        .work_report_use_cases
99        .list_work_reports_paginated(&page_request.into_inner(), &filters.into_inner())
100        .await
101    {
102        Ok(response) => HttpResponse::Ok().json(response),
103        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
104    }
105}
106
107/// Update work report
108#[put("/work-reports/{id}")]
109pub async fn update_work_report(
110    state: web::Data<AppState>,
111    user: AuthenticatedUser,
112    id: web::Path<Uuid>,
113    request: web::Json<UpdateWorkReportDto>,
114) -> impl Responder {
115    let organization_id = match user.require_organization() {
116        Ok(org_id) => org_id,
117        Err(e) => {
118            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
119        }
120    };
121
122    match state
123        .work_report_use_cases
124        .update_work_report(*id, request.into_inner())
125        .await
126    {
127        Ok(work_report) => {
128            AuditLogEntry::new(
129                AuditEventType::WorkReportUpdated,
130                Some(user.user_id),
131                Some(organization_id),
132            )
133            .with_resource("WorkReport", *id)
134            .log();
135
136            HttpResponse::Ok().json(work_report)
137        }
138        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
139    }
140}
141
142/// Delete work report
143#[delete("/work-reports/{id}")]
144pub async fn delete_work_report(
145    state: web::Data<AppState>,
146    user: AuthenticatedUser,
147    id: web::Path<Uuid>,
148) -> impl Responder {
149    let organization_id = match user.require_organization() {
150        Ok(org_id) => org_id,
151        Err(e) => {
152            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
153        }
154    };
155
156    match state.work_report_use_cases.delete_work_report(*id).await {
157        Ok(deleted) => {
158            if deleted {
159                AuditLogEntry::new(
160                    AuditEventType::WorkReportDeleted,
161                    Some(user.user_id),
162                    Some(organization_id),
163                )
164                .with_resource("WorkReport", *id)
165                .log();
166
167                HttpResponse::NoContent().finish()
168            } else {
169                HttpResponse::NotFound().json(serde_json::json!({
170                    "error": "Work report not found"
171                }))
172            }
173        }
174        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
175    }
176}
177
178// ==================== Warranty Management Endpoints ====================
179
180/// Get active warranties for a building
181#[get("/buildings/{building_id}/work-reports/warranties/active")]
182pub async fn get_active_warranties(
183    state: web::Data<AppState>,
184    building_id: web::Path<Uuid>,
185) -> impl Responder {
186    match state
187        .work_report_use_cases
188        .get_active_warranties(*building_id)
189        .await
190    {
191        Ok(warranties) => HttpResponse::Ok().json(warranties),
192        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
193    }
194}
195
196/// Get expiring warranties for a building (within X days)
197#[get("/buildings/{building_id}/work-reports/warranties/expiring")]
198pub async fn get_expiring_warranties(
199    state: web::Data<AppState>,
200    path: web::Path<Uuid>,
201    query: web::Query<serde_json::Value>,
202) -> impl Responder {
203    let building_id = path.into_inner();
204    let days = query.get("days").and_then(|v| v.as_i64()).unwrap_or(90) as i32;
205
206    match state
207        .work_report_use_cases
208        .get_expiring_warranties(building_id, days)
209        .await
210    {
211        Ok(warranties) => HttpResponse::Ok().json(warranties),
212        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
213    }
214}
215
216// ==================== Photo & Document Management ====================
217
218/// Add photo to work report
219#[post("/work-reports/{id}/photos")]
220pub async fn add_photo(
221    state: web::Data<AppState>,
222    user: AuthenticatedUser,
223    id: web::Path<Uuid>,
224    request: web::Json<AddPhotoDto>,
225) -> impl Responder {
226    let organization_id = match user.require_organization() {
227        Ok(org_id) => org_id,
228        Err(e) => {
229            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
230        }
231    };
232
233    match state
234        .work_report_use_cases
235        .add_photo(*id, request.into_inner())
236        .await
237    {
238        Ok(work_report) => {
239            AuditLogEntry::new(
240                AuditEventType::WorkReportPhotoAdded,
241                Some(user.user_id),
242                Some(organization_id),
243            )
244            .with_resource("WorkReport", *id)
245            .log();
246
247            HttpResponse::Ok().json(work_report)
248        }
249        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
250    }
251}
252
253/// Add document to work report
254#[post("/work-reports/{id}/documents")]
255pub async fn add_document(
256    state: web::Data<AppState>,
257    user: AuthenticatedUser,
258    id: web::Path<Uuid>,
259    request: web::Json<AddDocumentDto>,
260) -> impl Responder {
261    let organization_id = match user.require_organization() {
262        Ok(org_id) => org_id,
263        Err(e) => {
264            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
265        }
266    };
267
268    match state
269        .work_report_use_cases
270        .add_document(*id, request.into_inner())
271        .await
272    {
273        Ok(work_report) => {
274            AuditLogEntry::new(
275                AuditEventType::WorkReportDocumentAdded,
276                Some(user.user_id),
277                Some(organization_id),
278            )
279            .with_resource("WorkReport", *id)
280            .log();
281
282            HttpResponse::Ok().json(work_report)
283        }
284        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
285    }
286}