koprogo_api/infrastructure/web/handlers/
work_report_handlers.rs1use crate::application::dto::{
2 AddDocumentDto, AddPhotoDto, CreateWorkReportDto, PageRequest, UpdateWorkReportDto,
3 WorkReportFilters,
4};
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use crate::infrastructure::web::middleware::scope_guard::verify_acp_org_access;
7use crate::infrastructure::web::{AppState, AuthenticatedUser};
8use actix_web::{delete, get, post, put, web, HttpResponse, Responder, ResponseError};
9use uuid::Uuid;
10
11#[post("/work-reports")]
15pub async fn create_work_report(
16 state: web::Data<AppState>,
17 user: AuthenticatedUser,
18 request: web::Json<CreateWorkReportDto>,
19) -> impl Responder {
20 let organization_id = match user.require_organization() {
21 Ok(org_id) => org_id,
22 Err(e) => {
23 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
24 }
25 };
26
27 match state
28 .work_report_use_cases
29 .create_work_report({
43 let mut dto = request.into_inner();
44 dto.organization_id = organization_id.to_string();
45 dto
46 })
47 .await
48 {
49 Ok(work_report) => {
50 AuditLogEntry::new(
51 AuditEventType::WorkReportCreated,
52 Some(user.user_id),
53 Some(organization_id),
54 )
55 .with_resource("WorkReport", work_report.id.parse().unwrap())
56 .log();
57
58 HttpResponse::Created().json(work_report)
59 }
60 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
61 }
62}
63
64#[get("/work-reports/{id}")]
66pub async fn get_work_report(
67 state: web::Data<AppState>,
68 user: AuthenticatedUser,
69 id: web::Path<Uuid>,
70) -> impl Responder {
71 match state.work_report_use_cases.get_work_report(*id).await {
72 Ok(Some(work_report)) => {
73 let building_id = match uuid::Uuid::parse_str(&work_report.building_id) {
75 Ok(bid) => bid,
76 Err(_) => {
77 return HttpResponse::InternalServerError()
78 .json(serde_json::json!({"error": "Invalid building_id format"}))
79 }
80 };
81
82 match state.building_use_cases.get_building(building_id).await {
83 Ok(Some(building)) => {
84 let acp_id = match Uuid::parse_str(&building.acp_id) {
86 Ok(id) => id,
87 Err(_) => {
88 return HttpResponse::InternalServerError().json(serde_json::json!({
89 "error": "Invalid building.acp_id format"
90 }));
91 }
92 };
93 if let Err(err) =
94 verify_acp_org_access(&user, acp_id, &state.acp_use_cases).await
95 {
96 return err.error_response();
97 }
98 HttpResponse::Ok().json(work_report)
99 }
100 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
101 "error": "Building not found"
102 })),
103 Err(err) => {
104 HttpResponse::InternalServerError().json(serde_json::json!({"error": err}))
105 }
106 }
107 }
108 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
109 "error": "Work report not found"
110 })),
111 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
112 }
113}
114
115#[get("/buildings/{building_id}/work-reports")]
117pub async fn list_building_work_reports(
118 state: web::Data<AppState>,
119 building_id: web::Path<Uuid>,
120 user: AuthenticatedUser,
121) -> impl Responder {
122 if let Err(err) =
126 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
127 &user,
128 *building_id,
129 &state.building_use_cases,
130 &state.acp_use_cases,
131 )
132 .await
133 {
134 return err.error_response();
135 }
136
137 match state
138 .work_report_use_cases
139 .list_work_reports_by_building(*building_id)
140 .await
141 {
142 Ok(work_reports) => HttpResponse::Ok().json(work_reports),
143 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
144 }
145}
146
147#[get("/organizations/{organization_id}/work-reports")]
149pub async fn list_organization_work_reports(
150 state: web::Data<AppState>,
151 user: AuthenticatedUser,
152 organization_id: web::Path<Uuid>,
153) -> impl Responder {
154 if let Err(e) = user.verify_org_access(*organization_id) {
155 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
156 }
157 match state
158 .work_report_use_cases
159 .list_work_reports_by_organization(*organization_id)
160 .await
161 {
162 Ok(work_reports) => HttpResponse::Ok().json(work_reports),
163 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
164 }
165}
166
167#[get("/work-reports")]
169pub async fn list_work_reports_paginated(
170 state: web::Data<AppState>,
171 user: AuthenticatedUser,
172 page_request: web::Query<PageRequest>,
173 filters: web::Query<WorkReportFilters>,
174) -> impl Responder {
175 let mut filters = filters.into_inner();
182
183 match filters.organization_id {
184 Some(org_id) => {
185 if let Err(e) = user.verify_org_access(org_id) {
186 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
187 }
188 }
189 None => {
190 filters.organization_id = user.effective_org_filter();
191 }
192 }
193
194 if let Some(building_id) = filters.building_id {
195 if let Err(err) =
196 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
197 &user,
198 building_id,
199 &state.building_use_cases,
200 &state.acp_use_cases,
201 )
202 .await
203 {
204 return err.error_response();
205 }
206 }
207
208 match state
209 .work_report_use_cases
210 .list_work_reports_paginated(&page_request.into_inner(), &filters)
211 .await
212 {
213 Ok(response) => HttpResponse::Ok().json(response),
214 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
215 }
216}
217
218#[put("/work-reports/{id}")]
220pub async fn update_work_report(
221 state: web::Data<AppState>,
222 user: AuthenticatedUser,
223 id: web::Path<Uuid>,
224 request: web::Json<UpdateWorkReportDto>,
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 .update_work_report(*id, request.into_inner())
236 .await
237 {
238 Ok(work_report) => {
239 AuditLogEntry::new(
240 AuditEventType::WorkReportUpdated,
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#[delete("/work-reports/{id}")]
255pub async fn delete_work_report(
256 state: web::Data<AppState>,
257 user: AuthenticatedUser,
258 id: web::Path<Uuid>,
259) -> impl Responder {
260 let organization_id = match user.require_organization() {
261 Ok(org_id) => org_id,
262 Err(e) => {
263 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
264 }
265 };
266
267 match state.work_report_use_cases.delete_work_report(*id).await {
268 Ok(deleted) => {
269 if deleted {
270 AuditLogEntry::new(
271 AuditEventType::WorkReportDeleted,
272 Some(user.user_id),
273 Some(organization_id),
274 )
275 .with_resource("WorkReport", *id)
276 .log();
277
278 HttpResponse::NoContent().finish()
279 } else {
280 HttpResponse::NotFound().json(serde_json::json!({
281 "error": "Work report not found"
282 }))
283 }
284 }
285 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
286 }
287}
288
289#[get("/buildings/{building_id}/work-reports/warranties/active")]
293pub async fn get_active_warranties(
294 state: web::Data<AppState>,
295 building_id: web::Path<Uuid>,
296 user: AuthenticatedUser,
297) -> impl Responder {
298 if let Err(err) =
302 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
303 &user,
304 *building_id,
305 &state.building_use_cases,
306 &state.acp_use_cases,
307 )
308 .await
309 {
310 return err.error_response();
311 }
312
313 match state
314 .work_report_use_cases
315 .get_active_warranties(*building_id)
316 .await
317 {
318 Ok(warranties) => HttpResponse::Ok().json(warranties),
319 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
320 }
321}
322
323#[get("/buildings/{building_id}/work-reports/warranties/expiring")]
325pub async fn get_expiring_warranties(
326 state: web::Data<AppState>,
327 user: AuthenticatedUser,
328 path: web::Path<Uuid>,
329 query: web::Query<serde_json::Value>,
330) -> impl Responder {
331 let building_id = path.into_inner();
332 let days = query.get("days").and_then(|v| v.as_i64()).unwrap_or(90) as i32;
333
334 if let Err(err) =
338 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
339 &user,
340 building_id,
341 &state.building_use_cases,
342 &state.acp_use_cases,
343 )
344 .await
345 {
346 return err.error_response();
347 }
348
349 match state
350 .work_report_use_cases
351 .get_expiring_warranties(building_id, days)
352 .await
353 {
354 Ok(warranties) => HttpResponse::Ok().json(warranties),
355 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
356 }
357}
358
359#[post("/work-reports/{id}/photos")]
363pub async fn add_photo(
364 state: web::Data<AppState>,
365 user: AuthenticatedUser,
366 id: web::Path<Uuid>,
367 request: web::Json<AddPhotoDto>,
368) -> impl Responder {
369 let organization_id = match user.require_organization() {
370 Ok(org_id) => org_id,
371 Err(e) => {
372 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
373 }
374 };
375
376 match state
377 .work_report_use_cases
378 .add_photo(*id, request.into_inner())
379 .await
380 {
381 Ok(work_report) => {
382 AuditLogEntry::new(
383 AuditEventType::WorkReportPhotoAdded,
384 Some(user.user_id),
385 Some(organization_id),
386 )
387 .with_resource("WorkReport", *id)
388 .log();
389
390 HttpResponse::Ok().json(work_report)
391 }
392 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
393 }
394}
395
396#[post("/work-reports/{id}/documents")]
398pub async fn add_document(
399 state: web::Data<AppState>,
400 user: AuthenticatedUser,
401 id: web::Path<Uuid>,
402 request: web::Json<AddDocumentDto>,
403) -> impl Responder {
404 let organization_id = match user.require_organization() {
405 Ok(org_id) => org_id,
406 Err(e) => {
407 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
408 }
409 };
410
411 match state
412 .work_report_use_cases
413 .add_document(*id, request.into_inner())
414 .await
415 {
416 Ok(work_report) => {
417 AuditLogEntry::new(
418 AuditEventType::WorkReportDocumentAdded,
419 Some(user.user_id),
420 Some(organization_id),
421 )
422 .with_resource("WorkReport", *id)
423 .log();
424
425 HttpResponse::Ok().json(work_report)
426 }
427 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
428 }
429}